/**
 @file sys_tmm_serdes.c

 @date 2020-07-13

 @version v1.0

*/

/****************************************************************
 *
 * Header Files
 *
 ***************************************************************/
#include "ctc_error.h"
#include "ctc_port.h"
#include "sys_usw_chip.h"
#include "sys_usw_datapath.h"
#include "sys_usw_qos_api.h"
#include "sys_usw_mac.h"
#include "sys_usw_peri.h"
#include "sys_usw_dmps.h"
#include "sys_usw_wb_common.h"
#include "sys_usw_mcu.h"
#include "sys_usw_register.h"

#include "drv_api.h"
#include "usw/include/drv_chip_ctrl.h"
#include "usw/include/drv_common.h"
#include "sys_tmm_datapath.h"
#include "sys_tmm_serdes_fw.inc"
#include "sys_tmm_serdes.h"
#include "sys_tmm_serdes_reg.h"

extern const uint8 g_serdes_dbg_sw;
extern sal_file_t g_tmm_serdes_init_dump_fp;
extern int32
sys_tmm_serdes_get_internal_loopback_pcs(uint8 lchip, uint8 physic_serdes_id, uint8 *p_enable);
extern int32
_sys_tmm_mac_get_pcs_link_status(uint8 lchip, uint16 lport, uint32* p_is_up);

extern sys_datapath_master_t* p_usw_datapath_master[];

#define TSINGMA_DUMP_PRINT  if(g_serdes_dbg_sw && g_tmm_serdes_init_dump_fp) sal_fprintf

#define SYS_TMM_SERDES_TWOS_TO_INT(twos_val, bit_width)     (twos_val > 0 ? (twos_val):(twos_val + (1 << bit_width)))

#define SYS_TMM_SERDES_INT_TO_TOWS(twos_val, bit_width)     (((twos_val >> (bit_width - 1)) & 0x1) == 0 ? twos_val : (twos_val - (1 << bit_width)))

#define SYS_TMM_SERDES_ABS(val_1, val_2) ((val_1 - val_2 > 0) ? (val_1 - val_2):(val_2 - val_1))

#define SYS_TMM_SERDES_BIN_TO_GRAY(val) ((int)val ^ ((int)val >> 1))

#define SYS_TMM_SERDES_MSB_LSB_SWAP(x)     \
do{\
    typeof(x) _x = 0;\
    uint8 index = 0;\
    for(index = 0; index < sizeof(x); index ++) \
    {\
        _x += ((x >> 8*index) & 0xff) << (8*((sizeof(x) - 1 - index)));\
    }\
    x = _x;\
}while(0) 
    
#define sys_tmm_serdes_write_reg_by_bcast( lchip, hss_id, lane_bmp,  addr,  data)\
                  drv_chip_write_hss28g(lchip, hss_id, ((1 << 24) + (lane_bmp << 16) + addr), data)

int32 
sys_tmm_serdes_get_glb_info(uint8 lchip, uint8 physic_serdes_id, sys_tmm_serdes_glb_info_type_t type, uint8 *value)
{
    uint8 logical_serdes_id = 0;
    sys_datapath_serdes_info_t* p_serdes  = NULL;

    CTC_ERROR_RETURN(_sys_usw_datapath_get_logical_serdes_id_by_physical(lchip, physic_serdes_id, &logical_serdes_id));
    SYS_CONDITION_RETURN((SYS_TMM_USELESS_ID8 == logical_serdes_id), CTC_E_INVALID_PARAM);
    CTC_ERROR_RETURN(sys_usw_datapath_get_serdes_info(lchip, logical_serdes_id, &p_serdes));

    switch(type)
    {
        case SYS_TMM_SERDES_GLB_DATA_RATE:
            *value = p_serdes->mode;
            break;
        case SYS_TMM_SERDES_GLB_MD_MODE:
            *value = p_serdes->modulation_mode;
            break;
        case SYS_TMM_SERDES_GLB_TX_POL:
            *value = p_serdes->tx_polarity;
            break;
        case SYS_TMM_SERDES_GLB_RX_POL:
            *value = p_serdes->rx_polarity;
            break;
        case SYS_TMM_SERDES_GLB_OVCLK_SPEED:
            *value = p_serdes->overclocking_speed;
            break;
        case SYS_TMM_SERDES_GLB_FEC_TYPE:
            *value = p_serdes->fec_type;
            break;
        default:
            return CTC_E_INVALID_PARAM;
    }
    
    return CTC_E_NONE;
}

/*only update soft tables*/
int32 
sys_tmm_serdes_set_glb_info(uint8 lchip, uint8 physic_serdes_id, sys_tmm_serdes_glb_info_type_t type, uint8 value)
{
    uint8 logical_serdes_id = 0; 
    sys_datapath_serdes_info_t* p_serdes  = NULL;

    CTC_ERROR_RETURN(_sys_usw_datapath_get_logical_serdes_id_by_physical(lchip, physic_serdes_id, &logical_serdes_id));
    SYS_CONDITION_RETURN((SYS_TMM_USELESS_ID8 == logical_serdes_id), CTC_E_INVALID_PARAM);
    CTC_ERROR_RETURN(sys_usw_datapath_get_serdes_info(lchip, logical_serdes_id, &p_serdes));

    switch(type)
    {
        case SYS_TMM_SERDES_GLB_DATA_RATE:
            p_serdes->mode = value;
            break;
        case SYS_TMM_SERDES_GLB_MD_MODE:
            p_serdes->modulation_mode = value;
            break;
        case SYS_TMM_SERDES_GLB_TX_POL:
            p_serdes->tx_polarity = value;
            break;
        case SYS_TMM_SERDES_GLB_RX_POL:
            p_serdes->rx_polarity = value;
            break;
        case SYS_TMM_SERDES_GLB_BIT_WIDTH:
            p_serdes->bit_width= value;
            break;
        case SYS_TMM_SERDES_GLB_RATE_DIV:
            p_serdes->rate_div= value;
            break;
        case SYS_TMM_SERDES_GLB_OVCLK_SPEED:
            p_serdes->overclocking_speed= value;
            break;
        case SYS_TMM_SERDES_GLB_OPTICAL_MODE:
            p_serdes->optical_mode= value;
            break;
        default:
            return CTC_E_INVALID_PARAM;
    }
    
    return CTC_E_NONE;
}

int32 
sys_tmm_serdes_write_reg(uint8 lchip, uint8 serdes_id, uint16 addr, uint16 mask, uint16 data)
{
    uint16 raw_data = 0;
    uint16 fix_data = 0;
    uint16 mask_op = ~mask;
    uint8 bit = 0;
    uint8 hss_id = 0;
    uint32 lane_id = 0;
    uint32 final_addr = 0;
    uint8 start = 0;
    uint8 end =0;
    uint8 flag = 0, val = 0;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id %u, addr 0x%04x, mask 0x%04x, data 0x%x\n", serdes_id, addr, mask, data);

    for(bit = 0; bit < 16; bit ++)
    {
        val = (mask >> bit) & 0x1;
        if((val == 0) && (flag == 0))
        {
            start = bit;
            flag = 1;
        }
        if((val == 1) && (flag == 1))
        {
            end = bit - 1;
            break;
        }
    }
    if((mask == 0x0) ||(mask == 0x7fff))
    {
        end = 15;
    }

    if(start == end)
    {
        TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "serdes_id %u, write addr 0x%04x[%d] mask 0x%x  data 0x%x \n", serdes_id, addr, start, mask, data);
    }
    else
    {
        TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "serdes_id %u, write addr 0x%04x[%d, %d] mask 0x%x data 0x%x\n", serdes_id, addr, end, start, mask,data);
    }

    if (p_drv_master[lchip]->wb_status == DRV_WB_STATUS_RELOADING)
    {
        return CTC_E_NONE;
    }
    
    if ((SYS_TMM_MAX_SERDES_NUM + SYS_TMM_CPUMAC_SERDES_NUM) <= serdes_id)
    {
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_ERROR,"%% %s@%d: HSS serdes_id CANNOT be %d\n", __FUNCTION__, __LINE__, serdes_id);
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP,"%% %s@%d: HSS serdes_id CANNOT be %d\n", __FUNCTION__, __LINE__, serdes_id);
        return CTC_E_INVALID_PARAM;
    }
    if (0xffff == mask)
    {
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_ERROR,"%% %s@%d: HSS write mask CANNOT be all 1.\n", __FUNCTION__, __LINE__);
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP,"%% %s@%d: HSS write mask CANNOT be all 1.\n", __FUNCTION__, __LINE__);
        return CTC_E_INVALID_PARAM;
    }

    hss_id = SYS_TMM_MAP_SERDES_TO_HSS_IDX(serdes_id);
    lane_id = SYS_TMM_MAP_SERDES_TO_LANE_ID(serdes_id);

    final_addr = (lane_id << 16) + addr;

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "hss_id %d lane_id %d final_addr 0x%x\n", hss_id, lane_id, final_addr);

    if(0x0 == mask)
    {
        CTC_ERROR_RETURN(drv_chip_write_hss28g(lchip, hss_id, final_addr, data));
    }
    else
    {
        for(bit = 0; 0 == ((mask_op >> bit) & 0x1); bit++);
        
        fix_data = data << bit;
        
        CTC_ERROR_RETURN(drv_chip_read_hss28g(lchip, hss_id, final_addr, &raw_data));

        fix_data |= (raw_data & mask);
        CTC_ERROR_RETURN(drv_chip_write_hss28g(lchip, hss_id, final_addr, fix_data));
    }
    
    return CTC_E_NONE;
}

int32 
sys_tmm_serdes_read_reg(uint8 lchip, uint8 serdes_id, uint16 addr, uint16 mask, uint16 *data)
{
    uint16 raw_data = 0;
    uint16 mask_op = ~mask;
    uint8 bit = 0;
    uint8 hss_id = 0;
    uint32 lane_id = 0;
    uint32 final_addr = 0;
    int32 ret = 0;
    
    hss_id =SYS_TMM_MAP_SERDES_TO_HSS_IDX(serdes_id);
    lane_id = SYS_TMM_MAP_SERDES_TO_LANE_ID(serdes_id);
    final_addr = (lane_id << 16) + addr; 
    
    ret = drv_chip_read_hss28g(lchip, hss_id, final_addr, &raw_data);
    
    raw_data &= mask_op;
    for(bit = 0; 0 == ((mask_op >> bit) & 0x1); bit++);

    *data = raw_data >> bit;
    return ret;

}

int32
sys_tmm_serdes_set_domain_reset1(uint8 lchip, uint8 serdes_id, uint8 rst_type)
{
    uint16 rst_val[4] = {  SYS_TMM_SERDES_CHIP_LOGIC_RESET_VAL, /* LOGIC_RESET */
                           SYS_TMM_SERDES_CHIP_SOFT_RESET_VAL,  /* ALL_RESET */    
                           SYS_TMM_SERDES_CHIP_REG_RESET_VAL,  /* REGISTER_RESET */
                           SYS_TMM_SERDES_CHIP_CPU_RESET_VAL};/* CPU_RESET */
    if(SYS_TMM_SERDES_CPU_RESET < rst_type)
    {
        return CTC_E_INVALID_PARAM;
    }

    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, CHIP_RESET_ADDR, 0x0, rst_val[rst_type]));

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_domain_reset0(uint8 lchip, uint8 serdes_id, uint8 rst_type)
{
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id, CHIP_RESET_ADDR, 0x0, 0x0000));

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_domain_reset(uint8 lchip, uint8 serdes_id, uint16* p_val)
{
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip,serdes_id, CHIP_RESET_ADDR, 0x0, p_val));

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_domain_reset(uint8 lchip, uint8 serdes_id, uint8 rst_type)
{
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "serdes id %u, rst_type %u\n", serdes_id, rst_type);

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes id %u, rst_type %u\n", serdes_id, rst_type);

    CTC_ERROR_RETURN(sys_tmm_serdes_set_domain_reset1(lchip, serdes_id, rst_type));
    CTC_ERROR_RETURN(sys_tmm_serdes_set_domain_reset0(lchip, serdes_id, rst_type));

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_fw_cmd(uint8 lchip, uint8 serdes_id, uint16 cmd, uint16 detail, uint16 expected_response, uint16 *result)
{
    uint8 time_out = 1; //True
    uint16 cnt = 0;
    uint16 response = 0;

    if (p_drv_master[lchip]->wb_status == DRV_WB_STATUS_RELOADING)
    {
        return CTC_E_NONE;
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);
    
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM, "lchip %u, serdes_id %u, cmd %u, detail %u, expected_response %u\n", \
                                                                         lchip, serdes_id, cmd, detail, expected_response);
    if(detail != SYS_TMM_SERDES_PARAM_NONE)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, FW_CMD_DETAIL_ADDR, 0x0, detail));
    }

    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, FW_CMD_ADDR, 0x0, cmd));

    for(cnt = 0; cnt < 1000; cnt++)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_CMD_ADDR, 0x0, result));

        if((*result) != cmd)
        {
            time_out = 0; //False
            break;
        }
    }
    
    if (1 != SDK_WORK_PLATFORM)
    {
        for(cnt = 0; cnt < 1000; cnt++)
        {
            CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_CMD_ADDR, 0x0, result));

            if((*result) != cmd)
            {
                time_out = 0; //False
                break;
            }
        }
        if(time_out)
        {
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_ERROR,"%% Command 0x%04x timeout, is firmware running?\n",cmd);
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP,"Command 0x%04x timeout, is firmware running?\n",cmd);
            return CTC_E_HW_TIME_OUT;
        }

        if(expected_response != SYS_TMM_SERDES_PARAM_NONE)
        {
            response = ((*result) >> 8) & 0xf;
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "response 0x%x\n", response);
            
            if(response != expected_response)
            {
                SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_ERROR, "%% Error returned, code = 0x%04x\n",response);
                SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "Error returned, code = 0x%04x\n",response);
                return CTC_E_INIT_FAIL;
            }
        }
    }
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_CMD_ADDR, 0x0, result));
    }
    
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_fw_reg(uint8 lchip, uint8 serdes_id, uint16 fw_addr, uint16 section, uint16 *data)
{
    uint16 result;

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM, "lchip %u, serdes_id %u, fw_addr %u, section %u\n", lchip, serdes_id, fw_addr, section);

    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, FW_REG_READ_CMD + section, fw_addr, 0xe, &result));
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "result 0x%x\n", result);

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_REG_VALUE_ADDR, 0, data));

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_fw_reg(uint8 lchip, uint8 serdes_id,uint16 fw_addr, uint16 data, uint16 section)
{
    uint16 result;

    
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);


    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM, "lchip %u, serdes_id %u, fw_addr %u, section %u, data %u\n", lchip, serdes_id, fw_addr, section, data);
    
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, FW_REG_VALUE_ADDR, 0, data));
    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, FW_REG_WRITE_CMD + section, fw_addr, 0xe, &result));
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "result 0x%x\n", result);

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_fw_hash(uint8 lchip, uint8 serdes_id, uint32 *hash_code)
{
    uint16 result = 0;
    uint16 low_word = 0;

    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, FW_HASH_CMD, SYS_TMM_SERDES_PARAM_NONE, 0xf, &result));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_CMD_DETAIL_ADDR, 0x0, &low_word));
    
    *hash_code = ((result & 0xff) << 16) | low_word;

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_fw_crc(uint8 lchip, uint8 serdes_id, uint16 *checksum_code)
{
    uint16 result = 0;

    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, FW_CRC_CMD, SYS_TMM_SERDES_PARAM_NONE, 0xf, &result));
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "result 0x%x\n", result);
    
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_CMD_DETAIL_ADDR, 0x0, checksum_code));

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_init_fw_header(uint8 lchip)
{
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    p_usw_datapath_master[lchip]->fw_header.hash_code   = g_tmm_serdes_fw[0];
    p_usw_datapath_master[lchip]->fw_header.crc16       = (uint16)(g_tmm_serdes_fw[1] & 0xffff);
    p_usw_datapath_master[lchip]->fw_header.date        = (uint16)((g_tmm_serdes_fw[1] >> 16) & 0xffff);
    p_usw_datapath_master[lchip]->fw_header.entry_point = g_tmm_serdes_fw[2];
    p_usw_datapath_master[lchip]->fw_header.length      = g_tmm_serdes_fw[3];
    p_usw_datapath_master[lchip]->fw_header.ram_addr    = g_tmm_serdes_fw[4];

    SYS_TMM_SERDES_MSB_LSB_SWAP(p_usw_datapath_master[lchip]->fw_header.hash_code);
    SYS_TMM_SERDES_MSB_LSB_SWAP(p_usw_datapath_master[lchip]->fw_header.date);
    SYS_TMM_SERDES_MSB_LSB_SWAP(p_usw_datapath_master[lchip]->fw_header.crc16);
    SYS_TMM_SERDES_MSB_LSB_SWAP(p_usw_datapath_master[lchip]->fw_header.length);
    SYS_TMM_SERDES_MSB_LSB_SWAP(p_usw_datapath_master[lchip]->fw_header.entry_point);
    SYS_TMM_SERDES_MSB_LSB_SWAP(p_usw_datapath_master[lchip]->fw_header.ram_addr);

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, " Hash Code : 0x%06x\n", p_usw_datapath_master[lchip]->fw_header.hash_code);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, " Date Code : 0x%02x\n", p_usw_datapath_master[lchip]->fw_header.date);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "    CRC Code : 0x%04x\n", p_usw_datapath_master[lchip]->fw_header.crc16);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "    Length : %d\n", p_usw_datapath_master[lchip]->fw_header.length);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "    Entry : 0x%08x\n", p_usw_datapath_master[lchip]->fw_header.entry_point);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "    RAM : 0x%08x\n", p_usw_datapath_master[lchip]->fw_header.ram_addr);

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, " Hash Code : 0x%06x\n", p_usw_datapath_master[lchip]->fw_header.hash_code);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp,  " Date Code : 0x%02x\n", p_usw_datapath_master[lchip]->fw_header.date);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp,  "    CRC Code : 0x%04x\n", p_usw_datapath_master[lchip]->fw_header.crc16);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp,  "    Length : %d\n", p_usw_datapath_master[lchip]->fw_header.length);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp,  "    Entry : 0x%08x\n", p_usw_datapath_master[lchip]->fw_header.entry_point);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp,  "    RAM : 0x%08x\n", p_usw_datapath_master[lchip]->fw_header.ram_addr);

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

uint32
sys_tmm_serdes_get_fw_length(void)
{
    return (sizeof(g_tmm_serdes_fw)/sizeof(uint32));
}

int32
sys_tmm_serdes_fw_load_info(uint8 lchip, uint8 serdes_id, uint8 expected_hash_code, sys_tmm_serdes_fw_load_info_t *p_info)
{
    uint8  pam4_or_nrz = 0;
    uint16 fw_load_magic_word = 0;
    uint16 fw_load_info_code = 0;
    uint16 actual_magic_word = 0;
    uint32 actual_hash_code = 0;
    uint16 actual_crc_code = 0;

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_LOAD_MAGIC_WORD_ADDR, 0x0, &actual_magic_word));
    CTC_ERROR_RETURN(sys_tmm_serdes_fw_hash(lchip, serdes_id, &actual_hash_code));
    CTC_ERROR_RETURN(sys_tmm_serdes_fw_crc(lchip, serdes_id, &actual_crc_code));
    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));

    /*Firmware Downloaded and magic word is correct*/
    if(SYS_TMM_SERDES_PAM4_MODE != pam4_or_nrz)
    {
        fw_load_magic_word = FW_LOAD_MAGIC_WORD_NRZ;
    }
    else
    {
        fw_load_magic_word = FW_LOAD_MAGIC_WORD_PAM4;
    }

    if(actual_magic_word == fw_load_magic_word)
    {
        fw_load_info_code |= 1;
    }

    /* Firmware Downloaded and hash code is correct (hash code readback is valid only if FW is not halted)*/
    if(actual_hash_code == p_usw_datapath_master[lchip]->fw_header.hash_code)
    {
        fw_load_info_code |= 2;
    }

    /*Firmware Downloaded and crc code is correct*/
    if(actual_crc_code == p_usw_datapath_master[lchip]->fw_header.crc16)
    {
        fw_load_info_code |= 4;
    }

    p_info->load_info_code = fw_load_info_code;
    p_info->magic_word     = actual_magic_word;
    p_info->hash_code      = actual_hash_code;
    p_info->crc_code       = actual_crc_code;

    return CTC_E_NONE;
}

/**********************************************************************************
# This function checks:
#
# 1. Is the correct FW loaded?
***********************************************************************************/
int32 
sys_tmm_serdes_is_correct_fw_loaded(uint8 lchip, uint8 serdes_id, uint8 *stat)
{
    sys_tmm_serdes_fw_load_info_t load_info;

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    CTC_ERROR_RETURN(sys_tmm_serdes_fw_load_info(lchip, serdes_id, SYS_TMM_SERDES_PARAM_NONE, &load_info));

    /*SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "load_info_code %d\n", load_info.load_info_code);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "hash_code 0x%0x\n", load_info.hash_code);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "crc_code 0x%0x\n", load_info.crc_code);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "magic_word 0x%0x\n", load_info.magic_word);*/

    *stat = 1;

    /*Firmware Downloaded but Hash code is wrong*/
    if(load_info.hash_code != p_usw_datapath_master[lchip]->fw_header.hash_code)
    {
        *stat = 0;
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "hash code error\n");
        return CTC_E_NONE;
    }
 
    /*Firmware Downloaded but CRC code is wrong*/
    if(load_info.crc_code != p_usw_datapath_master[lchip]->fw_header.crc16)  
    {
        *stat = 0;
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "CRC code error\n");
        return CTC_E_NONE;
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_real_data_rate(uint8 datarate_mode, uint8 overclock_flag, uint32 *real_data_rate)
{
    uint32 data_rate_mapping_table[CTC_CHIP_MAX_SERDES_MODE] =
            {
                    0,        //SYS_TMM_SERDES_DATARATE_NONE
                    1031250,  //SYS_TMM_SERDES_DATARATE_XFI
                    125000,     //SYS_TMM_SERDES_DATARATE_SGMII
                    0,        //SYS_TMM_SERDES_DATARATE_XSGMII
                    500000,        //SYS_TMM_SERDES_DATARATE_QSGMII
                    312500,     //SYS_TMM_SERDES_DATARATE_XAUI
                    625000,     //SYS_TMM_SERDES_DATARATE_DXAUI
                    1031250,  //SYS_TMM_SERDES_DATARATE_XLG
                    2578125, //SYS_TMM_SERDES_DATARATE_CG
                    312500,      //SYS_TMM_SERDES_DATARATE_2DOT5G
                    1031250,  //SYS_TMM_SERDES_DATARATE_USGMII0
                    1031250,  //SYS_TMM_SERDES_DATARATE_USGMII1
                    1031250,  //SYS_TMM_SERDES_DATARATE_USGMII2
                    2578125,  //SYS_TMM_SERDES_DATARATE_XXVG
                    2578125,  //SYS_TMM_SERDES_DATARATE_LG
                    125000,     //SYS_TMM_SERDES_DATARATE_100BASEFX
                    5156250,   //SYS_TMM_SERDES_DATARATE_LG_R1
                    5156250,   //SYS_TMM_SERDES_DATARATE_CG_R2
                    5312500,   //SYS_TMM_SERDES_DATARATE_CCG_R4
                    5312500,   //SYS_TMM_SERDES_DATARATE_CDG_R8
            };

    uint32 overclock_data_rate_mapping_table[CTC_CHIP_MAX_SERDES_OCS_MODE] =
            {
                    0,          //CTC_CHIP_SERDES_OCS_MODE_NONE
                    1140625,    //CTC_CHIP_SERDES_OCS_MODE_11_06G
                    1250000,    //CTC_CHIP_SERDES_OCS_MODE_12_12G
                    1296875,    //CTC_CHIP_SERDES_OCS_MODE_12_58G
                    2812500,    //CTC_CHIP_SERDES_OCS_MODE_27_27G
                    1093750,    //CTC_CHIP_SERDES_OCS_MODE_10_6G
                    2656250,    //CTC_CHIP_SERDES_OCS_MODE_26_56G
                    2734375,    //CTC_CHIP_SERDES_OCS_MODE_26_52G
                    2778125,    //CTC_CHIP_SERDES_OCS_MODE_26_9G
                    3750000,    //CTC_CHIP_SERDES_OCS_MODE_36_36G
                    3906250,    //CTC_CHIP_SERDES_OCS_MODE_36_76G
                    1140625,    //CTC_CHIP_SERDES_OCS_MODE_11_06G11
                    1296875,    //CTC_CHIP_SERDES_OCS_MODE_12_58G12
                    5312500,    //CTC_CHIP_SERDES_OCS_MODE_51_56G
                    5625000,    //CTC_CHIP_SERDES_OCS_MODE_52_71G
                    2062500,    //CTC_CHIP_SERDES_OCS_MODE_20_625G
            };

    if (overclock_flag)
    {
        if(CTC_CHIP_MAX_SERDES_OCS_MODE <= overclock_flag)
        {
            return CTC_E_INVALID_PARAM;
        }

        *real_data_rate = overclock_data_rate_mapping_table[overclock_flag];
    }
    else
    {
        if(CTC_CHIP_MAX_SERDES_MODE <= datarate_mode)
        {
            return CTC_E_INVALID_PARAM;
        }

        *real_data_rate = data_rate_mapping_table[datarate_mode];
    }
    
    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_tx_taps(uint8 lchip, uint8 serdes_id, int8 *taps_param)
{
    uint16 data = 0;
#ifndef EMULATION_ENV
    int8 taps_data = 0;
#endif
    uint16 scale_data = 0;
    uint8 index = 0;
    uint8 abs_sum = 0;
    uint16 taps_reg_list[5][2] = {
            /* address        mask       */
            {TX_PRE2_ADDR,  TX_PRE2_MASK},
            {TX_PRE1_ADDR,  TX_PRE1_MASK},
            {TX_MAIN_ADDR,  TX_MAIN_MASK},
            {TX_POST1_ADDR, TX_POST1_MASK},
            {TX_POST2_ADDR, TX_POST2_MASK}};
            
    uint16 scale_mask[5] = {
             TX_PRE2_SCALE_MASK,
             TX_PRE1_SCALE_MASK,
             TX_MAIN_SCALE_MASK,
             TX_POST1_SCALE_MASK,
             TX_POST2_SCALE_MASK};

    CTC_PTR_VALID_CHECK(taps_param);

    /*param check*/
    if(taps_param[2] % 2)
    {
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "c2 only support even.\n");
        return CTC_E_INVALID_PARAM; 
    }
    
    for(index = 0; index < 5; index ++)
    {   
        abs_sum += SYS_TMM_SERDES_ABS(taps_param[index], 0);
    }
    
    if((abs_sum > 62) || ((abs_sum - SYS_TMM_SERDES_ABS(taps_param[2], 0)) >= SYS_TMM_SERDES_ABS(taps_param[2], 0)))
    {
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, \
                 "FFE parameter must meet :|c0| + |c1| +|c3| + |c4| < |c2| and |c0| +  |c1| +|C2| + |C3| + |c4| <= 62\n");
        return CTC_E_INVALID_PARAM; 
    }

    for(index = 0; index < 5; index ++)
    {
        if((taps_param[index] > 62) || (taps_param[index] < (-40)))
        {
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "Tx Taps must in -40 ~ 62\n");
            return CTC_E_INVALID_PARAM;
        }
        /*printf("taps[%d] = %d \n", index, taps_param[index]);*/
#ifndef EMULATION_ENV
        if(0 == (taps_param[index] % 2))
        {
            taps_data = taps_param[index] / 2;
            scale_data = 1;
        }
        else
        {
            taps_data = taps_param[index];
            scale_data = 0;
        }

        data = SYS_TMM_SERDES_TWOS_TO_INT(taps_data, 8);
#endif
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id, taps_reg_list[index][0], taps_reg_list[index][1], data));
        
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id, TX_TAPS_SCALE_ADDR, scale_mask[index], scale_data));
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_tx_taps(uint8 lchip, uint8 serdes_id, int8 *taps_param)
{
    uint16 data = 0;
    uint8 index = 0;
    uint16 scale_data = 0;
#ifndef EMULATION_ENV
    uint8 bit_width = 0;
#endif
    uint16 *p_reg = NULL;
    uint16 reg_list1[5][2] = {
            /* address         mask       */
            {TX_PRE2_ADDR,   TX_PRE2_MASK},
            {TX_PRE1_ADDR,   TX_PRE1_MASK},
            {TX_MAIN_ADDR,   TX_MAIN_MASK},
            {TX_POST1_ADDR,  TX_POST1_MASK},
            {TX_POST2_ADDR,  TX_POST2_MASK}};

    uint16 reg_list2[5][2] = {
             /* address            mask       */
            {TX_PRE2_ADDR,       TX_PRE2_MASK},
            {KR_SM_COEF_ADDR,    KR_SM_COEF_1_MASK},
            {KR_SM_COEF_ADDR,    KR_SM_COEF_0_MASK},
            {KR_SM_COEF_M_ADDR, KR_SM_COEF_M1_MASK},
            {KR_SM_COEF_M_ADDR, KR_SM_COEF_M2_MASK}};
    
    uint16 scale_mask[5] = {
             TX_PRE2_SCALE_MASK,
             TX_PRE1_SCALE_MASK,
             TX_MAIN_SCALE_MASK,
             TX_POST1_SCALE_MASK,
             TX_POST2_SCALE_MASK};

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, reg_list1[0][0], reg_list1[0][1], &data));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip,serdes_id, TX_TAPS_SCALE_ADDR, scale_mask[0], &scale_data));
#ifndef EMULATION_ENV
    taps_param[0] = SYS_TMM_SERDES_INT_TO_TOWS(data, 8);
    taps_param[0] = (scale_data == 0 ? taps_param[0] : (taps_param[0] * 2));
#endif

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, KR_SM_COEF_SEL_ADDR, KR_SM_COEF_SEL_MASK, &data));

    if(data)
    {
        /*In KR mode, it's [unused, pre2, pre1, main, post]*/
        p_reg = &reg_list2[0][0];
#ifndef EMULATION_ENV
        bit_width = 6;
#endif
    }
    else
    {
        p_reg = &reg_list1[0][0];
#ifndef EMULATION_ENV
        bit_width = 8;
#endif
    }

    for(index = 1; index < 5; index ++)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, *((p_reg + index * 2) + 0), *((p_reg + index * 2) + 1), &data));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip,serdes_id, TX_TAPS_SCALE_ADDR, scale_mask[index], &scale_data));
#ifndef EMULATION_ENV
        taps_param[index] = SYS_TMM_SERDES_INT_TO_TOWS(data, bit_width);
        taps_param[index] = (scale_data == 0 ? taps_param[index] : (taps_param[index] * 2));
#endif
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_signal_detect(uint8 lchip, uint8 physic_serdes_id, uint8* p_is_detect, uint8* p_raw_sigdet)
{
    uint8  hss_id    = SYS_TMM_MAP_SERDES_TO_HSS_IDX(physic_serdes_id);
    uint8  lane_id   = SYS_TMM_MAP_SERDES_TO_LANE_ID(physic_serdes_id);
    uint8  ctcxs_id  = 0;
    uint8  is_ctchs  = 0;
    uint32 tbl_id    = 0;
    uint32 fld_id    = 0;
    uint32 tbl_id_fc = 0;
    uint32 fld_id_fc = 0;
    uint32 cmd       = 0;
    uint32 index     = 0;
    uint32 value     = 0;
    uint32 value_fc  = 0;
    uint8  is_detect = 0;

    if(SYS_TMM_CPUMAC_HSS_ID == hss_id)
    {
        index     = 0;
        tbl_id    = CpuMacHssQuadMon_t;
        fld_id    = CpuMacHssQuadMon_monHssQuad_0_monHssSigDetect_f + 
                    (CpuMacHssQuadMon_monHssQuad_1_monHssSigDetect_f - CpuMacHssQuadMon_monHssQuad_0_monHssSigDetect_f) * lane_id;
        tbl_id_fc = SharedPcsSerdes0Cfg_t + (SharedPcsSerdes1Cfg_t - SharedPcsSerdes0Cfg_t) * lane_id;
        fld_id_fc = SharedPcsSerdes0Cfg_forceSignalDetect0_f;
    }
    else
    {
        SYS_TMM_GET_CTCXS_ID_BY_HSS_ID(hss_id, ctcxs_id, is_ctchs);
        index = DRV_INS(ctcxs_id, 0);
        if(is_ctchs)
        {
            if(0 == hss_id % 2)
            {
                tbl_id    = HssOctal0Mon_t;
                tbl_id_fc = HssOctal0Cfg_t;
            }
            else
            {
                tbl_id    = HssOctal1Mon_t;
                tbl_id_fc = HssOctal1Cfg_t;
            }
        }
        else
        {
            tbl_id    = HssOctalMon_t;
            tbl_id_fc = HssOctalCfg_t;
        }
        fld_id    = HssOctalMon_monHssOctal_0_monHssSigDetect_f + 
                    (HssOctalMon_monHssOctal_1_monHssSigDetect_f - HssOctalMon_monHssOctal_0_monHssSigDetect_f) * lane_id;
        fld_id_fc = HssOctalCfg_cfgHssOctal_0_cfgHssRxForceReady_f + 
                    (HssOctalCfg_cfgHssOctal_1_cfgHssRxForceReady_f - HssOctalCfg_cfgHssOctal_0_cfgHssRxForceReady_f) * lane_id;
    }

    cmd = DRV_IOR(tbl_id, fld_id);
    CTC_ERROR_RETURN(DRV_FIELD_IOCTL(lchip, index, cmd, &value));

    cmd = DRV_IOR(tbl_id_fc, fld_id_fc);
    CTC_ERROR_RETURN(DRV_FIELD_IOCTL(lchip, index, cmd, &value_fc));

    is_detect = value || value_fc;

    SYS_USW_VALID_PTR_WRITE(p_is_detect,  is_detect);
    SYS_USW_VALID_PTR_WRITE(p_raw_sigdet, value);

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_phyready(uint8 lchip, uint8 physic_serdes_id, uint32* value)
{
    uint8  hss_id    = SYS_TMM_MAP_SERDES_TO_HSS_IDX(physic_serdes_id);
    uint8  lane_id   = SYS_TMM_MAP_SERDES_TO_LANE_ID(physic_serdes_id);
    uint8  ctcxs_id  = 0;
    uint8  is_ctchs  = 0;
    uint32 tbl_id    = 0;
    uint32 fld_id    = 0;
    uint32 cmd       = 0;
    uint32 index     = 0;
    if(SYS_TMM_CPUMAC_HSS_ID == hss_id)
    {
        index     = 0;
        tbl_id    = CpuMacHssQuadMon_t;
        fld_id    = CpuMacHssQuadMon_monHssQuad_0_monHssPhyReady_f + 
                    (CpuMacHssQuadMon_monHssQuad_1_monHssPhyReady_f - CpuMacHssQuadMon_monHssQuad_0_monHssPhyReady_f) * lane_id;
    }
    else
    {
        SYS_TMM_GET_CTCXS_ID_BY_HSS_ID(hss_id, ctcxs_id, is_ctchs);
        index = DRV_INS(ctcxs_id, 0);
        if(is_ctchs)
        {
            if(0 == hss_id % 2)
            {
                tbl_id    = HssOctal0Mon_t;
            }
            else
            {
                tbl_id    = HssOctal1Mon_t;
            }
        }
        else
        {
            tbl_id    = HssOctalMon_t;
        }
        fld_id    = HssOctalMon_monHssOctal_0_monHssPhyReady_f + 
                    (HssOctalMon_monHssOctal_1_monHssPhyReady_f - HssOctalMon_monHssOctal_0_monHssPhyReady_f) * lane_id;
    }
    cmd = DRV_IOR(tbl_id, fld_id);
    CTC_ERROR_RETURN(DRV_FIELD_IOCTL(lchip, index, cmd, value));
    return CTC_E_NONE;
}

int32 
_sys_tmm_serdes_tx_taps_select(uint8 lchip, uint8 serdes_id, uint8 tx_idx)
{
    uint8 pam4_or_nrz = 0;
    int8 taps_param_list_nrz[7][5] = {
            {0, -4, 58, 0, 0},    /*0: 0dB ~ 5dB */
            {0, -6, 56, 0, 0},    /*1: 5dB ~ 10dB */
            {0, -8, 54, 0, 0},    /*2: 10dB ~15dB */
            {0, -10, 52, 0, 0},   /*3: 15dB ~ 20dB*/
            {0, -12, 50, 0, 0},   /*4: 20dB ~ 25dB*/
            {0, -14, 48, 0, 0},   /*5: 25dB ~ 30dB*/
            {0, -14, 48, 0, 0}};  /*6: 30dB ~ 35dB*/ 

    int8 taps_param_list_pam4[7][5] = {
            {0, -6, 50, 0, 0},    /*0: 0dB ~ 5dB */
            {0, -8, 50, 0, 0},    /*1: 5dB ~ 10dB */
            {2, -10, 50, 0, 0},   /*2: 10dB ~15dB */
            {2, -12, 48, 0, 0},   /*3: 15dB ~ 20dB*/
            {2, -14, 46, 0, 0},   /*4: 20dB ~ 25dB*/
            {4, -14, 44, 0, 0},   /*5: 25dB ~ 30dB*/
            {4, -16, 42, 0, 0}};  /*6: 30dB ~ 35dB*/ 

    if(6 < tx_idx)
    {
        return CTC_E_INVALID_PARAM;
    }

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));

    if(SYS_TMM_SERDES_PAM4_MODE == pam4_or_nrz)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_set_tx_taps(lchip, serdes_id, taps_param_list_pam4[tx_idx]));
    }
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_set_tx_taps(lchip, serdes_id, taps_param_list_nrz[tx_idx]));
    }

    return CTC_E_NONE;
}

uint16
_sys_tmm_serdes_get_pll_vcocap_from_table(uint32 fvco)
{
#define PLL_LVCOCAP_TAB_ITEM_MAX_MUN 116
    int32 index;
    uint16 vco_cap;
    uint32 pll_lvcocap_table[PLL_LVCOCAP_TAB_ITEM_MAX_MUN][2]={
            {3296875,   0},
            {3281250,   0},
            {3265625,   0},
            {3250000,   0},
            {3234375,   0},
            {3218750,   0},
            {3203125,   0},
            {3187500,   0},
            {3171875,   0},
            {3156250,   0},
            {3140625,   0},
            {3125000,   0},
            {3109375,   0},
            {3093750,   0},
            {3078125,   0},
            {3062500,   2},
            {3046875,   2},
            {3031250,   2},
            {3015625,   2},
            {3000000,   2},
            {2984375,   2},
            {2968750,   3},
            {2953125,   3},
            {2937500,   3},
            {2921875,   3},
            {2906250,   3},
            {2890625,   4},
            {2875000,   4},
            {2859375,   4},
            {2843750,   5},
            {2828125,   5},
            {2812500,   5},
            {2796875,   6},
            {2781250,   6},
            {2765625,   6},
            {2750000,   7},
            {2734375,   7},
            {2718750,   8},
            {2703125,   8},
            {2687500,   9},
            {2671875,   9},
            {2656250,  10},
            {2640625,  10},
            {2625000,  10},
            {2609375,  11},
            {2593750,  11},
            {2578125,  12},
            {2562500,  12},
            {2546875,  13},
            {2531250,  13},
            {2515625,  14},
            {2500000,  14},
            {2484375,  15},
            {2468750,  16},
            {2453125,  16},
            {2437500,  17},
            {2421875,  17},
            {2406250,  18},
            {2390625,  18},
            {2375000,  19},
            {2359375,  20},
            {2343750,  20},
            {2328125,  21},
            {2312500,  22},
            {2296875,  22},
            {2281250,  23},
            {2265625,  24},
            {2250000,  24},
            {2234375,  25},
            {2218750,  26},
            {2203125,  26},
            {2187500,  27},
            {2171875,  28},
            {2156250,  29},
            {2140625,  30},
            {2125000,  31},
            {2109375,  32},
            {2093750,  32},
            {2078125,  33},
            {2062500,  34},
            {2046875,  35},
            {2031250,  36},
            {2015625,  37},
            {2000000,  38},
            {1984375,  39},
            {1968750,  40},
            {1953125,  41},
            {1937500,  42},
            {1921875,  43},
            {1906250,  44},
            {1890625,  46},
            {1875000,  47},
            {1859375,  48},
            {1843750,  49},
            {1828125,  50},
            {1812500,  52},
            {1796875,  53},
            {1781250,  55},
            {1765625,  56},
            {1750000,  57},
            {1734375,  59},
            {1718750,  60},
            {1703125,  61},
            {1687500,  62},
            {1671875,  63},
            {1656250,  63},
            {1640625,  63},
            {1625000,  63},
            {1609375,  63},
            {1593750,  63},
            {1578125,  63},
            {1562500,  63},
            {1546875,  63},
            {1531250,  63},
            {1515625,  63},
            {1500000,  63}};


    index = PLL_LVCOCAP_TAB_ITEM_MAX_MUN - 1;

    /*start from bottom of table, lowest freqency, and move up*/
    while(1)
    {
        if((fvco <= pll_lvcocap_table[index][0]) && (index >= 0))
        {
            vco_cap = (uint16)pll_lvcocap_table[index][1];
            break;
        }
        else
        {
            index -= 1;
            if(index < 0)
            {
                index = 0;
                vco_cap = (uint16)pll_lvcocap_table[index][1];
                break;
            }
        }
    }

    return vco_cap;
}

int32
_sys_tmm_serdes_set_pll(uint8 lchip, uint8 serdes_id, uint8 data, uint8 ovclk_speed, uint8 mode, uint8 div4)
{   
    uint16 max_pll_n = 0;
    uint16 pll_n_val =0;
    uint32 real_data_rate = 0;
    uint32 desired_fvco;
    uint8 baud_rate_ratio;
    uint32 delta_fvco_m1;
    uint32 delta_fvco_0;
    uint32 delta_fvco_p1;
    uint32 calc_fvco;
    uint16 pll_cap_val;
    uint16 wr_data = 0;
    uint8 pam4_or_nrz = 0;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
    CTC_ERROR_RETURN(sys_tmm_serdes_get_real_data_rate(data, ovclk_speed, &real_data_rate));

    /*Set Div4*/
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, PLL_DIV4_ADDR, PLL_DIV4_MASK, div4));

    if(SYS_TMM_SERDES_PLL_SET_MODE_BY_DATA_RATE_FRE == mode)
    {
        baud_rate_ratio = (SYS_TMM_SERDES_PAM4_MODE == pam4_or_nrz ? 2 : 1);
        desired_fvco = real_data_rate / baud_rate_ratio;

        if(SYS_TMM_SERDES_PAM4_MODE ==  pam4_or_nrz)
        {
            desired_fvco = real_data_rate / 2;
        }
        /*NRZ Mode*/
        else
        {
            CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id,   0xb0, 0xfffe, &wr_data));/*0xb0[0]*/
            if(0 == wr_data)  /*Full Rate*/
            {
                desired_fvco = real_data_rate;
            }
            else
            {
                CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id,   0xa0, 0xfffc, &wr_data));/*0xa0[1:0]*/
                switch(wr_data)
                {
                    case 0 :
                        desired_fvco = real_data_rate * 2;              /*Half Rate*/
                        break;
                    case 1 :
                        desired_fvco = real_data_rate * 4;  /*Quarter Rate*/
                        break;
                    case 2 :
                        desired_fvco = real_data_rate * 8;  /*Eighth Rate*/
                        break;
                    case 3 :
                        desired_fvco = real_data_rate * 16;  /*Sixteenth Rate*/
                        break;
                    default :
                    break;
                }
            }
            
        }

        if(desired_fvco > 33 * 100000)
        {
            desired_fvco = 33 * 100000;
        }
        
        if(desired_fvco < 15 * 100000)
        {
            desired_fvco = 15 * 100000;
        }
        pll_n_val = (uint16)((1000 * desired_fvco / 100000 * (div4 ? 4 : 1)) / (4 * SYS_TMM_SERDES_REF_CLK_FREQ / 100));

        calc_fvco = (uint32)(SYS_TMM_SERDES_REF_CLK_FREQ * (pll_n_val - 1) * 4) / (div4 ? 4 : 1);
        delta_fvco_m1 = SYS_TMM_SERDES_ABS(desired_fvco, calc_fvco);

        calc_fvco = (uint32)(SYS_TMM_SERDES_REF_CLK_FREQ * pll_n_val * 4) /(div4 ? 4 : 1);
        delta_fvco_0 = SYS_TMM_SERDES_ABS(desired_fvco, calc_fvco);

        calc_fvco = (uint32)(SYS_TMM_SERDES_REF_CLK_FREQ * (pll_n_val + 1) * 4) / (div4 ? 4 : 1);
        delta_fvco_p1 = SYS_TMM_SERDES_ABS(desired_fvco, calc_fvco);

        if(delta_fvco_p1 < delta_fvco_0)
        {
            pll_n_val += 1;
        }
        else if(delta_fvco_m1 < delta_fvco_0)
        {
            pll_n_val -= 1;
        }

        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, PLL_N_DDR, PLL_N_MASK, pll_n_val));
    }
    else if(SYS_TMM_SERDES_PLL_SET_MODE_BY_PLL_N == mode)
    {
        max_pll_n = 0x1ff;
        if((int)real_data_rate > max_pll_n)
        {
            return CTC_E_INVALID_PARAM;
        }
        pll_n_val = ((int)real_data_rate) & max_pll_n;
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, PLL_N_DDR, PLL_N_MASK, pll_n_val));
    }

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, PLL_N_DDR, PLL_N_MASK, &pll_n_val));
    calc_fvco = (uint32)(SYS_TMM_SERDES_REF_CLK_FREQ * pll_n_val * 4) / (div4 ? 4 : 1);

    pll_cap_val = _sys_tmm_serdes_get_pll_vcocap_from_table(calc_fvco);
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, PLL_LVCOCAP_DDR, PLL_LVCOCAP_MASK, pll_cap_val));

    return CTC_E_NONE;
}

int32 _sys_tmm_serdes_set_prbs_pat(uint8 lchip, uint8 serdes_id, uint8 dir, uint8 pat_type, uint8 enable)
{
    uint8 pam4_or_nrz = 0;
    uint16 datapath_config_mapping[][5] =
            {
                /*prbstype, GrayCode, Precode, MSB-LSB*/
                    {0,        0,        0,       0},           /*0: NRZ-PRBS9*/
                    {1,        0,        0,       0},           /*1: NRZ-PRBS15*/
                    {2,        0,        0,       0},           /*2: NRZ-PRBS23*/
                    {3,        0,        0,       0},           /*3: NRZ-PRBS31*/        
                    {0,        0,        0,       0},           /*4 : PAM4-PRBS9*/
                    {1,        0,        0,       0},           /*5 : PAM4-PRBS13*/
                    {2,        0,        0,       0},           /*6 : PAM4-PRBS15*/
                    {3,        0,        0,       0},           /*7 : PAM4-PRBS31*/
                    {0,        1,        0,       0},           /*8 : PAM4-PRBS9Q*/
                    {1,        1,        0,       0},           /*9 : PAM4-PRBS13Q*/
                    {2,        1,        0,       0},           /*10 : PAM4-PRBS15Q*/
                    {3,        1,        0,       0},           /*11 : PAM4-PRBS31Q*/
                    {0,        1,        1,       0},           /*12 : PAM4-PRBS9QP*/
                    {1,        1,        1,       0},           /*13 : PAM4-PRBS13QP*/
                    {2,        1,        1,       0},           /*14 : PAM4-PRBS15QP*/
                    {3,        1,        1,       0},           /*15 : PAM4-PRBS31QP*/
            };

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));

    /*RX Configure*/
    if(dir == 0) 
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, RX_GC_EN_ADDR, RX_GC_EN_MASK, \
                                                                                  datapath_config_mapping[pat_type][1]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, RX_PC_EN_ADDR, RX_PC_EN_MASK, \
                                                                                  datapath_config_mapping[pat_type][2]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, RX_MSB_LSB_ADDR, RX_MSB_LSB_MASK, \
                                                                                  datapath_config_mapping[pat_type][3]));
            
        /*PAM4 PRBS patterns*/
        if(SYS_TMM_SERDES_PAM4_MODE == pam4_or_nrz)
        {
            /*0x43[6,5]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, PRBS_MODE_SEL_ADDR_PAM4, PRBS_MODE_SEL_MASK_PAM4, \
                                                                                       datapath_config_mapping[pat_type][0]));
            /*0x41[15]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, RX_PAM4_EN_ADDR, RX_PAM4_EN_MASK, 1));
            /*0x43[4,3] 0b11*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x43, 0xffe7, (enable ? 3 : 0)));
            /*0x44[15,8] 0h10*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x44, 0x00ff, (enable ? 0x10 : 0)));
            /*0x44[5] 0b1*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x44, 0xffdf, (enable ? 1 : 0)));
        }
        /*NRZ PRBS patterns*/
        else
        {
            /*0x161[13,12]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, PRBS_MODE_SEL_ADDR_NRZ, PRBS_MODE_SEL_MASK_NRZ, \
                                                                                     datapath_config_mapping[pat_type][0]));
            /*0x41[15]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, RX_PAM4_EN_ADDR, RX_PAM4_EN_MASK, 0));
            /*0x161[10]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x161, 0xfbff, (enable ? 1 : 0)));
        }
    }
    /*TX Configure*/
    else     
    {
        /*0xa0[9,8]*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_TEST_PATT_SEL_ADDR, TX_TEST_PATT_SEL_MASK, \
                                                                                  datapath_config_mapping[pat_type][0]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_GC_EN_ADDR, TX_GC_EN_MASK, \
                                                                                  datapath_config_mapping[pat_type][1]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_PC_EN_ADDR, TX_PC_EN_MASK, \
                                                                                  datapath_config_mapping[pat_type][2]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_MSB_LSB_ADDR, TX_MSB_LSB_MASK, 
                                                                                  datapath_config_mapping[pat_type][3]));

        /*PAM4 PRBS patterns*/
        if(SYS_TMM_SERDES_PAM4_MODE == pam4_or_nrz)
        {
            /*0xa0[15,13]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id, TX_TEST_PATT_CTRL_ADDR, 0x1fff, (enable ? 0x7 : 0)));
            /*0xa0[11]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id, 0xa0, 0xf7ff, (enable ? 1 : 0)));
        }
        /*NRZ PRBS patterns*/
        else
        {
            /*0xa0[15,13]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id, TX_TEST_PATT_CTRL_ADDR, 0x1fff, (enable ? 0x7 : 0)));
            /*0xa0[11]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id, 0xa0, 0xf7ff, (enable ? 1 : 0)));
            /*0xb0[11]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_NRZ_PRBS_EN_ADDR, TX_NRZ_PRBS_EN_MASK, (enable ? 1 : 0)));
            /*0xb0[14]*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_NRZ_PRBS_GEN_EN_ADDR, TX_NRZ_PRBS_GEN_EN_MASK, (enable ? 1 : 0)));
        }
    }

    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_get_prbs_pat_by_mode(uint8 lchip, uint8 serdes_id, uint8 prbs_mode, uint8 *pat_type)
{
    uint16 nrz_prbs_bitmap = 0x1d;     /*0b0 0000 0001 1101   "PRBS9", "PRBS15", "PRBS23", "PRBS31"*/
    uint16 pam4_prbs_bitmap = 0x1ff7;  /*0b1 1111 1111 0111   "PRBS9x", "PRBS13x", "PRBS15x", "PRBS31x"*/
    uint8 pam4_or_nrz = 0;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
    
    if(SYS_TMM_SERDES_NRZ_MODE == pam4_or_nrz)
    {
        /*Parameter Check*/
        if(1 != ((nrz_prbs_bitmap >> prbs_mode) & 0x1))
        {
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_ERROR, "%% NRZ Mode Only Support : PRBS9, PRBS15, PRBS23, PRBS31.\n");
            return CTC_E_INVALID_CONFIG;
        }

        switch(prbs_mode)
        {
            case 0:
               *pat_type = 0;
               break;
            case 2:
                *pat_type = 1;
                break;
            case 3:
                *pat_type = 2;
                break;
            case 4:
            default:
                *pat_type = 3;
                break;
        }
    }
    else
    {
        /*Parameter Check*/
        if(1 != ((pam4_prbs_bitmap >> prbs_mode) & 0x1))
        {
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_ERROR, "%% PAM4 Mode Don't   Support PRBS23.\n\n");
            return CTC_E_INVALID_CONFIG;
        }

        if((prbs_mode <= 4))
        {
            switch(prbs_mode)
            {
                case 0:
                    *pat_type = 4;
                    break;
                case 1:
                    *pat_type = 5;
                    break;
                case 2:
                    *pat_type = 6;
                    break;
                case 4:
                default:
                    *pat_type = 7;
                    break;
            }
        }
        else
        {
            *pat_type = (prbs_mode - 4) + 7;
        }
    }
    
    return CTC_E_NONE;
}

/********************************************************
 *TMM SerDes PRBS Mode
 *  0 : PRBS 9
 *  1 : PRBS13
 *  2 : PRBS15
 *  3 : PRBS23
 *  4 : PRBS31
 *  5 : PRBS9Q
 *  6 : PRBS13Q
 *  7 : PRBS15Q 
 *  8 : PRBS31Q
 *  9 : PRBS9QP
 *  10: PRBS13QP
 *  11: PRBS15QP
 *  12: PRBS31QP
 */
STATIC int32
_sys_tmm_serdes_get_prbs_param_proc(ctc_chip_serdes_prbs_t *p_prbs)
{
    switch(p_prbs->polynome_type)
    {
        /*PRBS9*/
        case 0:
            p_prbs->polynome_type = 8;/*PRBS9*/
            break;
        /*PRBS13*/
        case 1:
            p_prbs->polynome_type = 9;/*PRBS13*/
            break; 
        /*PRBS15*/
        case 2:
            p_prbs->polynome_type = 2;/*PRBS15+*/
            break; 
        /*PRBS23*/
        case 3:
            p_prbs->polynome_type = 4;/*PRBS23+*/
            break; 
        /*PRBS31*/
        case 4:
            p_prbs->polynome_type = 6;/*PRBS31+*/
            break; 
        /*PRBS9Q to PRBS31QP*/
        case 5:
        case 6:
        case 7:
        case 8:
        case 9:
        case 10:
        case 11:
        case 12:
        case 13:
            p_prbs->polynome_type += 5;/*PRBSxQ or PRBSxQP*/
            break;
        default:
            break;
    }
    
    return CTC_E_NONE;
}

STATIC int32
_sys_tmm_serdes_set_prbs_param_proc(ctc_chip_serdes_prbs_t *p_prbs)
{
    uint8 polynome_type = 0;
    
    switch(p_prbs->polynome_type)
    {
        /*PRBS15+ & PRBS15-*/
        case 2:
        case 3:
            polynome_type = 2;
            break;
        /*PRBS23+ & PRBS23-*/  
        case 4:
        case 5:
            polynome_type = 3;
            break;
        /*PRBS31+ & PRBS31-*/
        case 6:
        case 7:
            polynome_type = 4;
            break;
        /*PRBS9*/    
        case 8:
            polynome_type = 0;
            break;
        /*PRBS13*/
        case 9:
            polynome_type = 1;
            break;
        /*PRBS9Q to PRBS31QP*/
        case 10:
        case 11:
        case 12:
        case 13:
        case 14:
        case 15:
        case 16:
        case 17:
            polynome_type = p_prbs->polynome_type - 5;
            break;
        default :
            return CTC_E_INVALID_PARAM;
    }
    
    p_prbs->polynome_type = polynome_type;
    
    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_tx_prbs(uint8 lchip, ctc_chip_serdes_prbs_t *p_prbs)
{
    uint8 polynome_type_bak = 0;
    uint16 serdes_id = 0;
    uint8 pat_type = 0;
    uint8 enable;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, value:%d, type:%d, mode:%d\n", p_prbs->serdes_id, p_prbs->value, p_prbs->polynome_type, p_prbs->mode);

    serdes_id = p_prbs->serdes_id;
    polynome_type_bak = p_prbs->polynome_type;

    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_param_proc(p_prbs));

    enable = p_prbs->value ? TRUE : FALSE;

    CTC_ERROR_RETURN(_sys_tmm_serdes_get_prbs_pat_by_mode(lchip, serdes_id, p_prbs->polynome_type, &pat_type));

    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_pat(lchip, p_prbs->serdes_id, 1, pat_type, enable));

    p_prbs->polynome_type = polynome_type_bak;
    
    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_tx_prbs(uint8 lchip, ctc_chip_serdes_prbs_t *p_prbs)
{
    uint16 data   = 0;
    uint16 serdes_id = 0;
    uint16 pat_sel_val;
    uint8 nrz_prbs_pat[4] = {0, 2, 3, 4};/*"PRBS9", "PRBS15", "PRBS23", "PRBS31"*/ 
    uint8 pam4_prbs_pat[12] = {0, 1, 2, 4, 5, 6,  7, 8, 9, 10 , 11, 12};/*"PRBS9x", "PRBS13x", "PRBS15x", "PRBS31x"*/
    uint8 pam4_or_nrz = 0;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, mode:%d\n", p_prbs->serdes_id, p_prbs->mode);

    serdes_id = p_prbs->serdes_id;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
    
    if(SYS_TMM_SERDES_NRZ_MODE == pam4_or_nrz)
    {
        /*Get PRBS MODE*/
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, TX_TEST_PATT_SEL_ADDR, TX_TEST_PATT_SEL_MASK, &pat_sel_val));

        p_prbs->polynome_type = nrz_prbs_pat[pat_sel_val];
    }
    /*PAM4 Mode*/
    else
    {
        /*Get PRBS Pattern*/
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, TX_TEST_PATT_SEL_ADDR, TX_TEST_PATT_SEL_MASK, &pat_sel_val));
        /*Tx Gray Code and Pre Code 0xaf[9:8]*/
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, 0xaf, 0xfcff, &data));

        if(((data >> 1) & 0x1) == 1)/*Gray Code Enable*/
        {
            pat_sel_val += 4;
            pat_sel_val = pat_sel_val + (((data & 0x1) == 1) ? 4 : 0);
        }
        
        p_prbs->polynome_type = pam4_prbs_pat[pat_sel_val];
    }

    CTC_ERROR_RETURN(_sys_tmm_serdes_get_prbs_param_proc(p_prbs));

    /*check prbs whether is already enable.*/
    (void)sys_tmm_serdes_read_reg(lchip, serdes_id, 0xa0, 0xf7ff, &pat_sel_val);
    p_prbs->value = (0 == pat_sel_val ? 0 : 1);
    
    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_get_prbs_cnt(uint8 lchip, uint8 serdes_id, uint32 *err_cnt)
{
    uint16 data_low = 0;
    uint16 data_high = 0;
    uint8 pam4_or_nrz = 0;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
    
    if(SYS_TMM_SERDES_NRZ_MODE == pam4_or_nrz)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, NRZ_PRBS_CNT_ADDR, 0x0, &data_high));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, NRZ_PRBS_CNT_ADDR + 1, 0x0, &data_low));
    }
    else
    {   
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, PAM4_PRBS_CNT_ADDR, 0x0, &data_high));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, PAM4_PRBS_CNT_ADDR + 1, 0x0, &data_low));
    }

    *err_cnt = (data_high << 16) + data_low;

    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_set_prbs_cnt_rst(uint8 lchip, uint8 serdes_id)
{
    uint8 pam4_or_nrz = 0;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
    
    if(SYS_TMM_SERDES_NRZ_MODE ==  pam4_or_nrz)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x161, 0x7fff, 1));/*0x161[15]*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x161, 0x7fff, 0));/*0x161[15]*/
    }
    else
    {   
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x43, 0xfffe, 1));/*0x43[0]*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x43, 0xfffe, 0));/*0x43[15]*/
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_rx_prbs_check(uint8 lchip, ctc_chip_serdes_prbs_t *p_prbs)
{
    uint16 serdes_id = 0;
    uint32 err_cnt = 0;
    uint8 pat_type = 0;
    uint8 pam4_or_nrz = 0;
    uint8 polynome_type_bak = 0;
 
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__); 
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, value:%d, type:%d, mode:%d\n", p_prbs->serdes_id, p_prbs->value, p_prbs->polynome_type, p_prbs->mode);
    
    serdes_id = p_prbs->serdes_id;
    polynome_type_bak = p_prbs->polynome_type;

    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_param_proc(p_prbs));
    
    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
  
    CTC_ERROR_RETURN(_sys_tmm_serdes_get_prbs_pat_by_mode(lchip, serdes_id, p_prbs->polynome_type, &pat_type));
    
    /*1. PRBS Check Enable*/
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_pat(lchip, p_prbs->serdes_id, 0, pat_type, TRUE));

    /*2. Clear PRBS Error Count*/
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_cnt_rst(lchip, serdes_id));

    /*3. PRBS Check Status Read*/
    sal_task_sleep(1000);
    
    p_prbs->value = 1;

    CTC_ERROR_RETURN(_sys_tmm_serdes_get_prbs_cnt(lchip, serdes_id, &err_cnt));

    if(SYS_TMM_SERDES_NRZ_MODE == pam4_or_nrz)
    {
        if(0 != err_cnt)
        {   
            p_prbs->value = 0;
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "%% PRBS chek status error! err_cnt = %u\n", err_cnt);
        }
    }
    else
    {
        if(1000000 <= err_cnt)
        {    
            p_prbs->value = 0;
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "%% PRBS chek status error! err_cnt = %u\n", err_cnt);
        }
    }
    p_prbs->error_cnt = err_cnt;

    /*4. PRBS Check disable*/
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_pat(lchip, p_prbs->serdes_id, 0, pat_type, FALSE));

    p_prbs->polynome_type = polynome_type_bak;

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_rx_prbs_check(uint8 lchip, ctc_chip_serdes_prbs_t *p_prbs)
{
    uint16 data = 0;
    uint16 serdes_id = 0;
    uint16 pat_sel_val;
    uint8 pat_type = 0;
    uint8 nrz_prbs_pat[4] = {0, 2, 3, 4};/*"PRBS9", "PRBS15", "PRBS23", "PRBS31"*/ 
    uint8 pam4_prbs_pat[12] = {0, 1, 2, 4, 5, 6,  7, 8, 9, 10 , 11, 12};/*"PRBS9x", "PRBS13x", "PRBS15x", "PRBS31x"*/ 
    uint32 err_cnt = 0;
    uint8 pam4_or_nrz = 0;
    
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__); 
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, mode:%d\n", p_prbs->serdes_id, p_prbs->mode);
    
    serdes_id = p_prbs->serdes_id;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
    
    /*1. PRBS Mode Select*/
    if(SYS_TMM_SERDES_NRZ_MODE == pam4_or_nrz)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, PRBS_MODE_SEL_ADDR_NRZ, PRBS_MODE_SEL_MASK_NRZ, &pat_sel_val));
        p_prbs->polynome_type = nrz_prbs_pat[pat_sel_val];
    }
    /*PAM4 Mode*/
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, PRBS_MODE_SEL_ADDR, PRBS_MODE_SEL_MASK, &pat_sel_val));
        
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, RX_GC_EN_ADDR, RX_GC_EN_MASK, &data));
        pat_sel_val += data * 4;
        
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, RX_PC_EN_ADDR, RX_PC_EN_MASK, &data));
        pat_sel_val += data * 4;

        p_prbs->polynome_type = pam4_prbs_pat[pat_sel_val];
    }

    CTC_ERROR_RETURN(_sys_tmm_serdes_get_prbs_pat_by_mode(lchip, serdes_id, p_prbs->polynome_type, &pat_type));
    
    /*2. PRBS Check Enable*/
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_pat(lchip, p_prbs->serdes_id, 0, pat_type, TRUE));

    /*3. Clear PRBS Error Count*/
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_cnt_rst(lchip, serdes_id));

    /*4. PRBS Check Status Read*/
    sal_task_sleep(1000);
    CTC_ERROR_RETURN(_sys_tmm_serdes_get_prbs_cnt(lchip, serdes_id, &err_cnt));

    /*5. PRBS Check disable*/
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_prbs_pat(lchip, p_prbs->serdes_id, 0, pat_type, FALSE));

    CTC_ERROR_RETURN(_sys_tmm_serdes_get_prbs_param_proc(p_prbs));
    p_prbs->error_cnt = err_cnt;
    p_prbs->value = 1;
    if(SYS_TMM_SERDES_NRZ_MODE == pam4_or_nrz)
    {
        if(0 != err_cnt)
        {   
            p_prbs->value = 0;
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "%% PRBS chek status error! err_cnt = %u\n", err_cnt);
        }
    }
    else
    {
        if(1000000 <= err_cnt)
        {    
            p_prbs->value = 0;
            SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_DUMP, "%% PRBS chek status error! err_cnt = %u\n", err_cnt);
        }
    }
    
    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_set_datapath_prop(uint8 lchip, sys_tmm_serdes_datapath_prop_t prop_type, sys_tmm_serdes_datapath_prop_cfg_t *p_prop_cfg)
{
    uint8 serdes_id = 0;
    uint16 data;
    uint16 *p_addr = NULL;
    uint16 *p_mask = NULL;
                                                        /*gc      pc       msblsb*/
    uint16 tx_addr[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = { 0xaf,    0xaf,    0xaf};
    uint16 tx_mask[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = {0xfdff,   0xfeff,  0xfbff};

    uint16 rx_addr[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = { 0x42,    0x42,    0x43};
    uint16 rx_mask[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = {0xfffe,   0xfffd,  0x7fff};

    CTC_PTR_VALID_CHECK(p_prop_cfg);

    if(0 == p_prop_cfg->dir)
    {
        p_addr = rx_addr;
        p_mask = rx_mask;
    }
    else
    {
        p_addr = tx_addr;
        p_mask = tx_mask;
    }

    serdes_id = p_prop_cfg->serdes_id;

    switch(prop_type)
    {
        case SYS_TMM_SERDES_DATAPATH_PROP_GC:
        case SYS_TMM_SERDES_DATAPATH_PROP_PC:
        case SYS_TMM_SERDES_DATAPATH_PROP_MSBLSB:
            data = p_prop_cfg->mode_val;
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, p_addr[prop_type], p_mask[prop_type], data));
            break;
        default:
            return CTC_E_INVALID_PARAM;
    }

    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_get_datapath_prop(uint8 lchip, sys_tmm_serdes_datapath_prop_t prop_type, sys_tmm_serdes_datapath_prop_cfg_t *p_prop_cfg)
{
    uint8 serdes_id = 0;
    uint16 data;
    uint16 *p_addr = NULL;
    uint16 *p_mask = NULL;
                                                        /*gc      pc       msblsb*/
    uint16 tx_addr[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = { 0xaf,    0xaf,    0xaf};
    uint16 tx_mask[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = {0xfdff,   0xfeff,  0xfbff};

    uint16 rx_addr[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = { 0x42,    0x42,    0x43};
    uint16 rx_mask[SYS_TMM_SERDES_DATAPATH_PROP_MAX] = {0xfffe,   0xfffd,  0x7fff};

    CTC_PTR_VALID_CHECK(p_prop_cfg);

    if(0 == p_prop_cfg->dir)
    {
        p_addr = rx_addr;
        p_mask = rx_mask;
    }
    else
    {
        p_addr = tx_addr;
        p_mask = tx_mask;
    }

    serdes_id = p_prop_cfg->serdes_id;

    switch(prop_type)
    {
        case SYS_TMM_SERDES_DATAPATH_PROP_GC:
        case SYS_TMM_SERDES_DATAPATH_PROP_PC:
        case SYS_TMM_SERDES_DATAPATH_PROP_MSBLSB:
            CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, p_addr[prop_type], p_mask[prop_type], &data));
            break;
        default:
            return CTC_E_INVALID_PARAM;
    }
    
    p_prop_cfg->mode_val = data;

    return CTC_E_NONE;
}


int32
sys_tmm_serdes_set_polarity(uint8 lchip, uint8 serdes_id, uint8 dir, uint8 val)
{
    uint16 data;

    data = val;

    if(SYS_TMM_SERDES_RX == dir)
    {
        data = val;
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, RX_NRZ_POL_ADDR, RX_NRZ_POL_MASK, data));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, RX_PAM4_POL_ADDR, RX_PAM4_POL_MASK, data));
        CTC_ERROR_RETURN(sys_tmm_serdes_set_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_RX_POL, val));
    }
    else
    {
        data = (val ? 0 : 1);
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_POL_ADDR, TX_POL_MASK, data));
        CTC_ERROR_RETURN(sys_tmm_serdes_set_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_TX_POL, val));
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_polarity(uint8 lchip, uint8 serdes_id, uint8 dir, uint8 *val)
{
    uint16 data;

    if(0 == dir)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, RX_NRZ_POL_ADDR, RX_NRZ_POL_MASK, &data));
        *val = (uint8)data; 
    }
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, TX_POL_ADDR, TX_POL_MASK, &data));
        *val = (data ? 0 : 1); 
    }

    return CTC_E_NONE;
}

/*AGC*/
int32
sys_tmm_serdes_set_ctle(uint8 lchip, uint8 serdes_id, uint16 index, uint16 c1, uint16 c2)
{
    uint8 pam4_or_nrz = 0;
    uint16 ctle_map0_addr = CTLE_MAP0_ADDR_PAM4;
    uint16 ctle_map1_addr = CTLE_MAP1_ADDR_PAM4;
    uint16 ctle_map2_addr = CTLE_MAP2_ADDR_PAM4;
    uint16 ctle_val_addr = CTLE_VAL_ADDR_PAM4;
    uint16 ctle_val_mask = CTLE_VAL_MASK_PAM4;
    uint16 ctle_en_addr = CTLE_EN_ADDR_PAM4;
    uint16 ctle_en_mask = CTLE_EN_MASK_PAM4;
    
    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));

    if(SYS_TMM_SERDES_PAM4_MODE != pam4_or_nrz)
    {
        ctle_map0_addr = CTLE_MAP0_ADDR_NRZ;
        ctle_map1_addr = CTLE_MAP1_ADDR_NRZ;
        ctle_map2_addr = CTLE_MAP2_ADDR_NRZ;
        ctle_val_addr = CTLE_VAL_ADDR_NRZ;
        ctle_val_mask = CTLE_VAL_MASK_NRZ;
        ctle_en_addr = CTLE_EN_ADDR_NRZ;
        ctle_en_mask = CTLE_EN_MASK_NRZ;
    }
    
    /*make sure CTLE OW is enabled*/
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_en_addr, ctle_en_mask, 1));
    
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_val_addr, ctle_val_mask, index));
    
    switch(index)
    {
        case 0:
           CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map2_addr, 0x1fff, c1));
           CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map2_addr, 0xe3ff,c2));
           break;
        case 1:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map2_addr, 0xfc7f, c1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map2_addr, 0xff8f, c2));
            break;
        case 2:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map2_addr, 0xfff1, c1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map2_addr, 0xfffe, c2>>2));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map1_addr, 0x3fff, c2&3));
            break;
        case 3:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map1_addr, 0xc7ff, c1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map1_addr, 0xf8ff, c2));
            break;
        case 4:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map1_addr, 0xff1f, c1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map1_addr, 0xffe3, c2));
            break;
        case 5:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map1_addr, 0xfffc, c1 >> 1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map0_addr, 0x7fff, c1 & 1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map0_addr, 0x8fff, c2));
            break;
        case 6:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map0_addr, 0xf1ff, c1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map0_addr, 0xfe3f, c2));
            break;
        case 7:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map0_addr, 0xffc7, c1));
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_map0_addr, 0xfff8, c2));
            break;
        default:
            return CTC_E_INVALID_PARAM;
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_ctle(uint8 lchip, uint8 serdes_id, uint16 *index, uint16 *c1, uint16 *c2)
{
    uint8 pam4_or_nrz = 0;
    uint16 map[8][2];
    uint16 data;
    uint16 ctle_map0_addr = CTLE_MAP0_ADDR_PAM4;
    uint16 ctle_map1_addr = CTLE_MAP1_ADDR_PAM4;
    uint16 ctle_map2_addr = CTLE_MAP2_ADDR_PAM4;
    uint16 ctle_val_addr = CTLE_VAL_ADDR_PAM4;
    uint16 ctle_val_mask = CTLE_VAL_MASK_PAM4;
    uint16 ctle_en_addr = CTLE_EN_ADDR_PAM4;
    uint16 ctle_en_mask = CTLE_EN_MASK_PAM4;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));

    if(SYS_TMM_SERDES_PAM4_MODE != pam4_or_nrz)
    {
        ctle_map0_addr = CTLE_MAP0_ADDR_NRZ;
        ctle_map1_addr = CTLE_MAP1_ADDR_NRZ;
        ctle_map2_addr = CTLE_MAP2_ADDR_NRZ;
        ctle_val_addr = CTLE_VAL_ADDR_NRZ;
        ctle_val_mask = CTLE_VAL_MASK_NRZ;
        ctle_en_addr = CTLE_EN_ADDR_NRZ;
        ctle_en_mask = CTLE_EN_MASK_NRZ;
    }

    /*make sure CTLE OW is enabled*/
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ctle_en_addr, ctle_en_mask, 1));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map2_addr, 0x1fff, &map[0][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map2_addr, 0xe3ff, &map[0][1]));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map2_addr, 0xfc7f, &map[1][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map2_addr, 0xff8f, &map[1][1]));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map2_addr, 0xfff1, &map[2][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map2_addr, 0xfffe, &data));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map1_addr , 0x3fff, &map[2][1]));
    map[2][1] += (data << 2);

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map1_addr , 0xc7ff, &map[3][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map1_addr , 0xf8ff, &map[3][1]));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map1_addr , 0xff1f, &map[4][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map1_addr , 0xffe3, &map[4][1]));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map1_addr , 0xfffc, &data));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map0_addr, 0x7fff, &map[5][0]));
    map[5][0] += (data << 1);
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map0_addr, 0x8fff, &map[5][1]));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map0_addr, 0xf1ff, &map[6][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map0_addr, 0xfe3f, &map[6][1]));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map0_addr, 0xffc7, &map[7][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_map0_addr, 0xfff8, &map[7][1]));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, ctle_val_addr, ctle_val_mask, &data));

    *index = data;
    *c1 = map[*index][0];
    *c2 = map[*index][1];

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_fw_auto_ctle(uint8 lchip, uint8 serdes_id, uint8 is_auto)
{
    uint16 addr = FW_TUNING_CTRL_ADDR;
    uint16 mask = FW_TUNING_CTRL_CTLE_MODE_MASK_NRZ;
    uint16 data = 0;

    if(!is_auto)
    {
        data = 1; //Manual CTLE
    }
    else
    {
        data = 0;//FW Auto CTLE
    }

    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip,serdes_id,addr,mask,data));

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_tx_en(uint8 lchip, uint8 serdes_id, uint8 enable)
{    
    if ((SYS_TMM_MAX_SERDES_NUM + SYS_TMM_CPUMAC_SERDES_NUM) <= serdes_id)
    {
        return CTC_E_INVALID_PARAM;
    }

    if(enable)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xA0, 0x1fff, 0x2));
    }
    else
    {
        /*Transmit all 0 data, make link down*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_TEST_PATT_ADDR_4, 0x0, 0x0000));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_TEST_PATT_ADDR_3, 0x0, 0x0000));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_TEST_PATT_ADDR_2, 0x0, 0x0000));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, TX_TEST_PATT_ADDR_1, 0x0, 0x0000));

        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xA0, 0x1fff, 0x1));
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_tx_en(uint8 lchip, uint8 serdes_id, uint8* p_en)
{
    uint16 idx = 0;
    uint8  en  = 0;
    uint16 addr[5] = {
        0xA0, 
        TX_TEST_PATT_ADDR_1, 
        TX_TEST_PATT_ADDR_2, 
        TX_TEST_PATT_ADDR_3, 
        TX_TEST_PATT_ADDR_4
    };
    uint16 mask[5] = {
        0x1fff, 
        0x0, 
        0x0, 
        0x0, 
        0x0
    };
    uint16 value_dis[5] = {
        0x1, 
        0x0, 
        0x0, 
        0x0, 
        0x0
    };
    uint16 value[5] = {0};

    if((SYS_TMM_MAX_SERDES_NUM + SYS_TMM_CPUMAC_SERDES_NUM) <= serdes_id)
    {
        return CTC_E_INVALID_PARAM;
    }

    for(idx = 0; idx < 5; idx++)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, addr[idx], mask[idx], &(value[idx])));
        if(value[idx] != value_dis[idx])
        {
            en = 1;
            break;
        }
    }
    SYS_USW_VALID_PTR_WRITE(p_en, en);

    return CTC_E_NONE;
}

STATIC int32
_sys_tmm_serdes_get_pam4_eye(uint8 lchip, uint16 serdes_id, uint32 *eye_margin)
{
    uint32 eye_mV;
    uint32 eye_mV_min   = 0xffff;
    uint16 fw_debug_cmd = 0xB000;
    uint16 mode         = 0xa;
    uint16 cmd          = fw_debug_cmd + ((mode & 0xf) << 4);
    uint16 reg_fw_data0 = 0x9f00;
    uint16 index        = 4;
    uint16 data         = 0;
    uint16 eye_index;

    for(eye_index = 0; eye_index < 3; eye_index ++)
    {
        /*_ctc_tmm_dkit_misc_serdes_PT_debug*/
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, cmd, index, 0xb, &data));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_CMD_DETAIL_ADDR, 0x0, &data));
        
        /*_ctc_tmm_dkit_misc_serdes_fw_read_s16*/
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, (reg_fw_data0 + eye_index), 0x0, &data));
        eye_mV = ((data & 0x8000) ? (data - 0x10000) : data);

        if(eye_mV < eye_mV_min)
        {
            eye_mV_min = eye_mV;
        }
    }

    SYS_USW_VALID_PTR_WRITE(eye_margin, eye_mV_min);

    return CTC_E_NONE;
}


int32
sys_tmm_serdes_get_eye(uint8 lchip, uint8 serdes_id, uint32 *eye_margin)
{
    uint8 pam4_or_nrz = 0;
    uint16 dacq_reg_val;
    uint16 eye_reg_val;
    uint32 eye_mV = 0;

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));

    /*NRZ Mode Eye Margin*/
    if(SYS_TMM_SERDES_PAM4_MODE != pam4_or_nrz)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, DAC_SEL_ADDR_NRZ, DAC_SEL_MASK_NRZ, &dacq_reg_val));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, EYE_READ_ADDR_NRZ, EYE_READ_MASK_NRZ, &eye_reg_val));

        /*NRZ Eye Formula*/
        if(eye_reg_val > 0)
        {
            eye_mV = (eye_reg_val * 10000 / 2048) * (200 + (50 * dacq_reg_val));
        }

        *eye_margin = eye_mV / 10000;
    }
    /*PAM4 Mode Eye Margin*/
    else
    {
        CTC_ERROR_RETURN(_sys_tmm_serdes_get_pam4_eye(lchip, serdes_id, eye_margin));
    }

    

    return CTC_E_NONE;
}

int32 
_sys_tmm_serdes_set_rx_tx_serial_loopback(uint8 lchip, uint8 serdes_id, uint8 enable)
{
    uint8 list_size = 0;
    uint8 index = 0;
    uint8 pam4_or_nrz = 0;
    uint16 tuning_done = 0;
    uint8 signal_detect = 0;
    uint32 times_cnt = 0;
    uint16 (*p_cfg_mapping)[3] = NULL;
    uint16 nrz_en_cfg_mapping[][3] = {
        /*addr    mask     val*/
        {0x00cb,  0xfffb,  0x1}, /*0x00cb[2] = 1*/
        {0x00cd,  0xff1f,  0x7}, /*0x00cd[7,5] = 0x7*/
        {0x00fe,  0xfff7,  0x0}, /*0x00fe[3] = 0*/
        {0x00a0,  0x00ff,  0x0}, /*0x00a0[15:8] = 0*/
        {0x9803,  0x7fff,  0x1}, /*0x9803[15] = 1*/
        {0x0161,  0xfeff,  0x0}, /*0x161[8] = 0*/
    };

    uint16 nrz_dis_cfg_mapping[][3] = {
        /*addr    mask     val*/
        {0x0161,  0xfeff,  0x1}, /*0x161[8] = 1*/
        {0x9803,  0x7fff,  0x0}, /*0x9803[15] = 0*/
        {0x00a0,  0x00ff,  0x40}, /*0x00a0[15:8] = 0x40*/
        {0x00fe,  0xfff7,  0x1}, /*0x00fe[3] = 1*/
        {0x00cd,  0xff1f,  0x0}, /*0x00cd[7,5] = 0x0*/
        {0x00cb,  0xfffb,  0x0}, /*0x00cb[2] = 0*/
    };
    
    uint16 pam4_en_cfg_mapping[][3] = {
        /*addr    mask     val*/
        {0x00cb,  0xfffb,  0x1}, /*0x00cb[2] = 1*/
        {0x00cd,  0xff1f,  0x7}, /*0x00cd[7,5] = 0x7*/
        {0x00ce,  0xdfff,  0x0}, /*0x00ce[13] = 0*/
        {0x00ce,  0xe001,  0x0}, /*0x00ce[12,1] = 0*/
        {0x004b,  0xe7ff,  0x2}, /*0x004b[12,11] = 2*/
        {0x00a0,  0x00ff,  0x0}, /*0xa0[15,8] = 0*/
        {0x9803,  0x7fff,  0x1}, /*0x9803[15] = 1*/
    };

    uint16 pam4_dis_cfg_mapping[][3] = {
        /*addr    mask     val*/
        {0x9803,  0x7fff,  0x0}, /*0x9803[15] = 0*/
        {0x00a0,  0x00ff,  0x40}, /*0xa0[15,8] = 0x40*/
        {0x004b,  0xe7ff,  0x1}, /*0x004b[12,11] = 1*/
        {0x00ce,  0xe001,  0x0}, /*0x00ce[12,1] = 0*/
        {0x00ce,  0xdfff,  0x0}, /*0x00ce[13] = 0*/
        {0x00cd,  0xff1f,  0x0}, /*0x00cd[7,5] = 0x0*/
        {0x00cb,  0xfffb,  0x0}, /*0x00cb[2] = 0*/
    };

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, &pam4_or_nrz));
    CTC_ERROR_RETURN(sys_tmm_serdes_get_signal_detect(lchip, serdes_id, NULL, &signal_detect));

    if(signal_detect)
    {
        for(times_cnt = 0; times_cnt < 80000; times_cnt ++)
        {
            CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, 0x9811, 0xfffb, &tuning_done));

            if(tuning_done)
            {
                break;
            }
        }
    }

    if(times_cnt >= 80000)
    {
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_ERROR, "%% [DATAPATH]     SerDes %d is tunning, Please wait it done.\n", serdes_id);
        return CTC_E_NOT_SUPPORT;
    }

    if(SYS_TMM_SERDES_NRZ_MODE == pam4_or_nrz)
    {
        list_size = 6;
        p_cfg_mapping = (enable ? nrz_en_cfg_mapping : nrz_dis_cfg_mapping); 
    }
    else
    {
        list_size = 7;
        p_cfg_mapping = (enable ? pam4_en_cfg_mapping : pam4_dis_cfg_mapping); 
    }

    for(index = 0; index < list_size; index ++)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, p_cfg_mapping[index][0], p_cfg_mapping[index][1], p_cfg_mapping[index][2]));
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_rx_tx_serial_loopback(uint8 lchip, uint8 serdes_id, uint8 *enable)
{
    uint16 val_0xcd = 0;
    uint16 val_0xcb = 0;

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, 0xcd, 0xff1f, &val_0xcd));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, 0xcb, 0xfffb, &val_0xcb));

    if((0x7 == val_0xcd) || (0x1 == val_0xcb))
    {
        *enable = 1;//ENABLED
    }
    else
    {
        *enable = 0;//Disabled
    }

    return CTC_E_NONE;

}

int32 
_sys_tmm_serdes_get_tuning_done_status(uint8 lchip, uint8 serdes_id, uint8 *stat)
{
    uint16 data;
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_TUNING_STATUS_ADDR, FW_TUNING_STATUS_DONE_MASK, &data));

    *stat = (uint8)data;
    
    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_tx_rx_serial_loopback(uint8 lchip, uint8 serdes_id, uint8 *enable)
{
    uint16 rx_mode;
    uint16 tx_mode;

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, 0xca, 0xfffd, &rx_mode));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, 0xc5, 0x7fff, &tx_mode));

    if((rx_mode== 1) && (tx_mode == 1))
    {
        *enable = 1;//ENABLED
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "\n....TX-to-RX Serial Loopback ENABLED!\n");
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "\n....Note:  Requires a 100-ohm Termination between TX pins (or connect TX to another lane's RX)\n");
    }
    else
    {
        *enable = 0;//Disabled
    }

    return CTC_E_NONE;
}

int32 
_sys_tmm_serdes_config_pam4_fw_tx(uint8 lchip, uint8 serdes_id, uint8 tx_idx)
{
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xAF, 0, 0xF83E));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xB0, 0, 0x0));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xB0, 0, 0x800));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xB0, 0, 0x0));

    CTC_ERROR_RETURN(_sys_tmm_serdes_tx_taps_select(lchip, serdes_id, tx_idx));

    return CTC_E_NONE;
}

int32 
_sys_tmm_serdes_config_pam4_fw_rx(uint8 lchip, uint8 serdes_id, uint8 optical_mode)
{

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "lchip %u, serdes_id %u\n", lchip, serdes_id);

    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xefff, 1));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xfbff, 1));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xf7ff, 1));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xfdff, 1));

    if(optical_mode)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xfeff, 1));
    }
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xfeff, 0));
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);
    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_config_nrz_fw_tx(uint8 lchip, uint8 serdes_id, uint8 tx_idx, uint8 half_rate)
{
    uint16 data = 0; 
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xAF, 0, 0xF83E));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xB0, 0, 0x4802));

    data = (half_rate ? 1 : 0);
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xB0, 0xfffe, data));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xB0, 0xf7ff, 0x0));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xB0, 0xf7ff, 0x1));

    CTC_ERROR_RETURN(_sys_tmm_serdes_tx_taps_select(lchip, serdes_id, tx_idx));

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);
    return CTC_E_NONE;
}

int32 
_sys_tmm_serdes_config_nrz_fw_rx(uint8 lchip, uint8 serdes_id, uint8 optical_mode)
{
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "lchip %u, serdes_id %u, optical_mode %u\n", lchip, serdes_id, optical_mode);

    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xf7ff, 1));

    if(optical_mode)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xfeff, 1));
    }
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980f, 0xfeff, 0));
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    //CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x0161, 0x03ff, 0x1d));

    /*Disable PRBS Check 0x161[10] = 0*/
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x0161, 0xfbff, 0));

    return CTC_E_NONE;
}

/*All the external connection that is not firmware-related*/
int32
_sys_tmm_serdes_config_connection(uint8 lchip, sys_tmm_serdes_serdes_config_param_t *p_data)
{
    uint8 serdes_id = 0;
    uint8 rx_polarity = 0;
    uint8 tx_polarity = 0;
    sys_tmm_serdes_datapath_prop_cfg_t p_prop_cfg;
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    CTC_PTR_VALID_CHECK(p_data);

    serdes_id = p_data->serdes_id;
    
    if(p_data->rx_input_mode)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xF7, 0xefff, 0));
    }
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xF7, 0xefff, 1));
    }
    
    /*Rx*/
    p_prop_cfg.dir = 0;
    p_prop_cfg.serdes_id = p_data->serdes_id;
    p_prop_cfg.mode_val = p_data->rx.gray_code;
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_datapath_prop(lchip, SYS_TMM_SERDES_DATAPATH_PROP_GC, &p_prop_cfg));

    p_prop_cfg.mode_val = p_data->rx.pre_code;
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_datapath_prop(lchip, SYS_TMM_SERDES_DATAPATH_PROP_PC, &p_prop_cfg));

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_RX_POL, &rx_polarity));  

    CTC_ERROR_RETURN(sys_tmm_serdes_set_polarity(lchip, serdes_id, SYS_TMM_SERDES_RX, rx_polarity));

    p_prop_cfg.mode_val = p_data->rx.msblsb;
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_datapath_prop(lchip, SYS_TMM_SERDES_DATAPATH_PROP_MSBLSB, &p_prop_cfg));
    /*printf("RX: serdes_id: %d, gray_code: %d, pre_code: %d, msblsb: %d \n",
        p_prop_cfg.serdes_id, p_data->rx.gray_code, p_data->rx.pre_code, p_data->rx.msblsb);*/

    /*Tx*/
    p_prop_cfg.dir = 1;
    p_prop_cfg.mode_val = p_data->tx.gray_code;
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_datapath_prop(lchip, SYS_TMM_SERDES_DATAPATH_PROP_GC, &p_prop_cfg));

    p_prop_cfg.mode_val = p_data->tx.pre_code;
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_datapath_prop(lchip, SYS_TMM_SERDES_DATAPATH_PROP_PC, &p_prop_cfg));

    CTC_ERROR_RETURN(sys_tmm_serdes_get_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_TX_POL, &tx_polarity));

    CTC_ERROR_RETURN(sys_tmm_serdes_set_polarity(lchip, serdes_id, SYS_TMM_SERDES_TX, tx_polarity));

    p_prop_cfg.mode_val = p_data->tx.msblsb;
    CTC_ERROR_RETURN(_sys_tmm_serdes_set_datapath_prop(lchip, SYS_TMM_SERDES_DATAPATH_PROP_MSBLSB, &p_prop_cfg));
    /*printf("TX: serdes_id: %d, gray_code: %d, pre_code: %d, msblsb: %d \n",
        p_prop_cfg.serdes_id, p_data->tx.gray_code, p_data->tx.pre_code, p_data->tx.msblsb);*/

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);
    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_fw_config_speed_pam4(uint8 lchip, sys_tmm_serdes_fw_config_param_t *p_data, 
                                                sys_tmm_serdes_serdes_config_param_t *p_serdes)
{
    uint8  serdes_id;
    uint16 detail;
    uint16 link_train_option;
    uint16 result;
    uint32 cmd;
    uint32 value = 1;
    McPcsX8LanesRsv_m lt_mode;

    cmd = DRV_IOR(McPcsX8LanesRsv_t, DRV_ENTRY_FLAG);
    CTC_ERROR_RETURN(DRV_IOCTL(lchip, 0, cmd, &lt_mode));
    DRV_IOR_FIELD(lchip, McPcsX8LanesRsv_t, McPcsX8LanesRsv_rsv_0_rsv_f, &value, &lt_mode);
    if((1 == value) && (SYS_TMM_SERDES_LINK_TRAIN_TYPE_ENABLE == p_data->link_train))
    {
        p_data->link_train = SYS_TMM_SERDES_LINK_TRAIN_TYPE_PRBS;
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);
    serdes_id = p_data->serdes_id;
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "lchip %u, serdes_id %u\n", lchip, serdes_id);

    detail = p_serdes->datarate_val;

    if(p_data->link_train)
    {
        detail |= (1 << 8);
    }

    /*Set LT option firmware register*/
    CTC_ERROR_RETURN(sys_tmm_serdes_get_fw_reg(lchip, serdes_id, 0, 4, &link_train_option));

    if(SYS_TMM_SERDES_LINK_TRAIN_TYPE_PRBS == p_data->link_train)
    {
        link_train_option |= (1 << 0);
    }
    else
    {
        link_train_option &= ~(1 << 0);
    }

    link_train_option &= ~(1 << 3);
    
    CTC_ERROR_RETURN(sys_tmm_serdes_set_fw_reg(lchip, serdes_id, 0, link_train_option, 4));

    CTC_ERROR_RETURN(_sys_tmm_serdes_config_pam4_fw_tx(lchip ,serdes_id, p_data->tx_idx));

    if(p_data->link_train)
    {
        /*Send data*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xA0, 0xdfff, 0));        
    }

    CTC_ERROR_RETURN(_sys_tmm_serdes_config_pam4_fw_rx(lchip, serdes_id, p_data->optical_mode));

    CTC_ERROR_RETURN(_sys_tmm_serdes_config_connection(lchip, p_serdes));

    /*Set Data Rate By Firmware*/
    if (0 == p_serdes->datarate_set_mode)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980F, 0xFF7F, 0));
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, 0x80D0, detail, 8, &result));
    }
    /*Set Data Rate By Configure PLL*/
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980F, 0xFF7F, 1));
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, 0x80D0, detail, 8, &result));
        sal_task_sleep(10);
        CTC_ERROR_RETURN(_sys_tmm_serdes_set_pll(lchip, serdes_id, p_data->data_rate, p_data->ovclk_speed, SYS_TMM_SERDES_PLL_SET_MODE_BY_DATA_RATE_FRE,1));
    }
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "result 0x%x\n", result);
    
    if(p_data->link_train)
    {
        /*Disable Tx Graycode and Precodein LT*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xaf, 0xffcf, 0));

        /*Disable Rx Graycode and Precodein LT*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x42, 0xfffc, 0));
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_fw_config_speed_nrz(uint8 lchip, sys_tmm_serdes_fw_config_param_t *p_data, 
                                                sys_tmm_serdes_serdes_config_param_t *p_serdes)
{
    uint8 serdes_id;
    uint16 detail;
    uint16 link_train_option;
    uint16 result;
    uint32 cmd;
    uint32 value = 1;
    McPcsX8LanesRsv_m lt_mode;

    cmd = DRV_IOR(McPcsX8LanesRsv_t, DRV_ENTRY_FLAG);
    CTC_ERROR_RETURN(DRV_IOCTL(lchip, 0, cmd, &lt_mode));
    DRV_IOR_FIELD(lchip, McPcsX8LanesRsv_t, McPcsX8LanesRsv_rsv_0_rsv_f, &value, &lt_mode);
    if((1 == value) && (SYS_TMM_SERDES_LINK_TRAIN_TYPE_ENABLE == p_data->link_train))
    {
        p_data->link_train = SYS_TMM_SERDES_LINK_TRAIN_TYPE_PRBS;
    }

    serdes_id = p_data->serdes_id;

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    detail = p_serdes->datarate_val;

    if(p_data->link_train)
    {
        detail |= (1 << 8);
        if(p_data->lt_nrz_init)
        {
            detail |= (1 << 10);
        }
    }
    /*printf("NRZ FW config detail: 0x%X \n", detail);*/

    /*Set LT option firmware register*/
    CTC_ERROR_RETURN(sys_tmm_serdes_get_fw_reg(lchip, serdes_id, 0, 4, &link_train_option));

    if(SYS_TMM_SERDES_LINK_TRAIN_TYPE_PRBS == p_data->link_train)
    {
        link_train_option |= (1 << 0);
    }
    else
    {
        link_train_option &= ~(1 << 0);
    }

    link_train_option &= ~(1 << 3);
    
    CTC_ERROR_RETURN(sys_tmm_serdes_set_fw_reg(lchip, serdes_id, 0, link_train_option, 4));

    CTC_ERROR_RETURN(_sys_tmm_serdes_config_nrz_fw_tx(lchip, serdes_id, p_data->tx_idx, p_serdes->nrz_sub_rate));

    if(p_data->link_train)
    {
        /*Send data*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xA0, 0xdfff, 0));        
    }

    CTC_ERROR_RETURN(_sys_tmm_serdes_config_nrz_fw_rx(lchip, serdes_id, p_data->optical_mode));

    CTC_ERROR_RETURN(_sys_tmm_serdes_config_connection(lchip, p_serdes));

    /*Set Data Rate By Firmware*/
    if (0 == p_serdes->datarate_set_mode)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980F, 0xFF7F, 0));
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, 0x80C0, detail, 8, &result));
    }
    /*Set Data Rate By Configure PLL*/
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980F, 0xFF7F, 1));
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, 0x80C0, detail, 8, &result));
        sal_task_sleep(10);
        CTC_ERROR_RETURN(_sys_tmm_serdes_set_pll(lchip, serdes_id, p_data->data_rate, p_data->ovclk_speed, SYS_TMM_SERDES_PLL_SET_MODE_BY_DATA_RATE_FRE,1));
    }
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "result 0x%x\n", result);

    /*CPUMAC sgmii workaround*/
    if(((CTC_CHIP_SERDES_SGMII_MODE == p_data->data_rate) || (CTC_CHIP_SERDES_2DOT5G_MODE == p_data->data_rate))
                                            && (serdes_id >= SYS_TMM_CPUMAC_SERDES_START_ID))
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_set_domain_reset(lchip, serdes_id, SYS_TMM_SERDES_LOGIC_RESET));
        sal_task_sleep(20);
        /*C9[14,13] = 0b00*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xc9, 0x9fff, 0x0));
        /*C9[14,13] = 0b11*/
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0xc9, 0x9fff, 0x3));
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

/***********************************************************************************************************************
1)traning state: training_state = BE_debug(lane_num, 2, 1) # 0: disable, 1: running, 2: done
2)training FFE value: tap0 = BE_debug(lane_num, 2, 2); tap1 = BE_debug(lane_num, 2, 3); tapm1 = BE_debug(lane_num, 2, 4)
************************************************************************************************************************/
STATIC int32
_sys_tmm_serdes_get_link_training_status(uint8 lchip, uint16 serdes_id, uint16* p_value)
{
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM, "lchip %u, serdes_id %u\n", lchip, serdes_id);

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, LINK_TRAINING_CONTROL_ADDR, 0, &p_value[0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_LINK_TRAINING_STATUS_ADDR, 0, &p_value[1]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, LINK_TRAINING_STATUS_ADDR, 0, &p_value[2]));

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_serdes_globals(uint8 lchip, uint8 data_rate, uint8 ovclk_speed, sys_tmm_serdes_serdes_config_param_t *p_data)
{
    uint32 real_data_rate = 0;
    uint8 data_rate_level = 0;
    uint8 serdes_id = 0;
    uint8 bit_width = 0;
    uint8 rate_div = 0;
    uint8 cfg_mapping[SYS_TMM_SERDES_MAX_DATARATE_LEVEL][3] = {
        /*  PAM4 or NRZ                          data rate config val              nrz sub rate*/
        {SYS_TMM_SERDES_NRZ_MODE,    SYS_TMM_SERDES_DATARATE_VAL_NRZ_1,     SYS_TMM_SERDES_NRZ_SUB_DATA_TRUE},//SYS_TMM_SERDES_DATARATE_LEVEL_0_TO_2
        {SYS_TMM_SERDES_NRZ_MODE,    SYS_TMM_SERDES_DATARATE_VAL_NRZ_3,     SYS_TMM_SERDES_NRZ_SUB_DATA_TRUE},//SYS_TMM_SERDES_DATARATE_LEVEL_2_TO_4
        {SYS_TMM_SERDES_NRZ_MODE,    SYS_TMM_SERDES_DATARATE_VAL_NRZ_5,     SYS_TMM_SERDES_NRZ_SUB_DATA_TRUE},//SYS_TMM_SERDES_DATARATE_LEVEL_4_TO_6
        {SYS_TMM_SERDES_NRZ_MODE,    SYS_TMM_SERDES_DATARATE_VAL_NRZ_10,    SYS_TMM_SERDES_NRZ_SUB_DATA_TRUE},//SYS_TMM_SERDES_DATARATE_LEVEL_6_TO_12
        {SYS_TMM_SERDES_NRZ_MODE,    SYS_TMM_SERDES_DATARATE_VAL_NRZ_16,    SYS_TMM_SERDES_NRZ_SUB_DATA_TRUE},//SYS_TMM_SERDES_DATARATE_LEVEL_12_TO_16
        {SYS_TMM_SERDES_NRZ_MODE,    SYS_TMM_SERDES_DATARATE_VAL_NRZ_25,    SYS_TMM_SERDES_NRZ_SUB_DATA_FLASE},//SYS_TMM_SERDES_DATARATE_LEVEL_16_TO_27
        {SYS_TMM_SERDES_NRZ_MODE,    SYS_TMM_SERDES_DATARATE_VAL_NRZ_28,    SYS_TMM_SERDES_NRZ_SUB_DATA_FLASE},//SYS_TMM_SERDES_DATARATE_LEVEL_27_TO_30
        {SYS_TMM_SERDES_PAM4_MODE, SYS_TMM_SERDES_DATARATE_VAL_PAM4_37, SYS_TMM_SERDES_NRZ_SUB_DATA_NULL},//SYS_TMM_SERDES_DATARATE_LEVEL_30_TO_40
        {SYS_TMM_SERDES_PAM4_MODE, SYS_TMM_SERDES_DATARATE_VAL_PAM4_53, SYS_TMM_SERDES_NRZ_SUB_DATA_NULL},//SYS_TMM_SERDES_DATARATE_LEVEL_40_TO_54
        {SYS_TMM_SERDES_PAM4_MODE, SYS_TMM_SERDES_DATARATE_VAL_PAM4_56, SYS_TMM_SERDES_NRZ_SUB_DATA_NULL},//SYS_TMM_SERDES_DATARATE_LEVEL_54_TO_58
    };

    CTC_PTR_VALID_CHECK(p_data);
    serdes_id = p_data->serdes_id;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM, "lchip %u, serdes_id %u\n", lchip, serdes_id);

    CTC_ERROR_RETURN(sys_tmm_serdes_get_real_data_rate(data_rate, ovclk_speed, &real_data_rate));

    if(real_data_rate < (2 * 100000))
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_HEXL;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_0_TO_2;
    }
    else if(real_data_rate < (4 * 100000))
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_EIGHTH;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_2_TO_4;
    }
    else if(real_data_rate < (6 * 100000))
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_QUAD;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_4_TO_6;
    }
    else if(real_data_rate < (12 * 100000))
    {
        rate_div = (real_data_rate < (725 * 1000) ? SYS_TMM_SERDES_RATE_DIV_QUAD : SYS_TMM_SERDES_RATE_DIV_HALF);
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_6_TO_12;
    }
    else if(real_data_rate < (16 * 100000))
    {
        rate_div = (real_data_rate < (145 * 10000) ? SYS_TMM_SERDES_RATE_DIV_HALF : SYS_TMM_SERDES_RATE_DIV_FULL);
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_12_TO_16;
    }
    else if(real_data_rate < (27 * 100000))
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_FULL;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_16_TO_27;
    }
    else if(real_data_rate < (30 * 100000))
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_FULL;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_27_TO_30;
    }
    else if(real_data_rate < (40 * 100000))
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_FULL;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_30_TO_40;
    }
    else if(real_data_rate < (54 * 100000))
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_FULL;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_40_TO_54;
    }
    else
    {
        rate_div = SYS_TMM_SERDES_RATE_DIV_FULL;
        data_rate_level = SYS_TMM_SERDES_DATARATE_LEVEL_54_TO_58;
    }

    p_data->pam4_or_nrz = cfg_mapping[data_rate_level][0];
    p_data->datarate_val = cfg_mapping[data_rate_level][1];
    p_data->nrz_sub_rate = cfg_mapping[data_rate_level][2];

    p_data->prbs_pattern = SYS_TMM_SERDES_PRBS_MODE_31;

    
    if(((CTC_CHIP_SERDES_SGMII_MODE == data_rate) || (CTC_CHIP_SERDES_2DOT5G_MODE == data_rate))
                                                         && (serdes_id >= SYS_TMM_CPUMAC_SERDES_START_ID))
    {
        /*CpuMac, Use 20T clock*/
        bit_width = SYS_TMM_SERDES_BW_20BITS;
    }
    else
    {
        /*PAM4 : 64BITS,  NRZ : 32BITS*/
        bit_width = (SYS_TMM_SERDES_PAM4_MODE == p_data->pam4_or_nrz ?  SYS_TMM_SERDES_BW_64BITS : SYS_TMM_SERDES_BW_32BITS); 
    }

    CTC_ERROR_RETURN(sys_tmm_serdes_set_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_MD_MODE, cfg_mapping[data_rate_level][0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_set_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_BIT_WIDTH, bit_width));
    CTC_ERROR_RETURN(sys_tmm_serdes_set_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_RATE_DIV, rate_div));

    if(SYS_TMM_SERDES_PAM4_MODE != p_data->pam4_or_nrz)
    {
        p_data->baud_rate_ratio = 1;
    }
    else
    {
        p_data->baud_rate_ratio = 2;   
        p_data->tx.gray_code = 1;
        p_data->rx.gray_code = 1;
    }

    switch(real_data_rate)
    {
        case 125000:
        case 312500:
        case 500000:
        case 1031250:
        case 2578125:
        case 2812500:
        case 5312500:
        case 5625000:
            p_data->datarate_set_mode = 0;/*Set Data Rate By Firmware*/
            break;
        default:
            p_data->datarate_set_mode = 1;/*Set Data Rate By Configure PLL*/
            break;
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "%s end \n", __FUNCTION__);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n#########################\n");

    return CTC_E_NONE;
}

/*fw_cmd -> fw_reg(2)==0x123 -> logic rst -> set go bit*/
int32
_sys_tmm_serdes_fw_config(uint8 lchip, sys_tmm_serdes_fw_config_param_t *p_data)
{
    uint8  serdes_id = 0;
#ifndef EMULATION_ENV
    uint16 response  = 0;
    uint8  times     = 10;
#endif
    sys_tmm_serdes_serdes_config_param_t serdes_cfg = {0};

    if (p_drv_master[lchip]->wb_status == DRV_WB_STATUS_RELOADING)
    {
        return CTC_E_NONE;
    }

    CTC_PTR_VALID_CHECK(p_data);

    serdes_id = p_data->serdes_id;

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n#########################\n");
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "%s start\n", __FUNCTION__);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "lchip %u, serdes_id %u\n", lchip, serdes_id);

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM, "lchip %u, serdes_id %u\n", lchip, serdes_id);

    CTC_ERROR_RETURN(sys_tmm_serdes_set_glb_info(lchip, serdes_id, SYS_TMM_SERDES_GLB_DATA_RATE, p_data->data_rate));

    serdes_cfg.serdes_id = serdes_id;
    CTC_ERROR_RETURN(sys_tmm_serdes_set_serdes_globals(lchip, p_data->data_rate, p_data->ovclk_speed, &serdes_cfg));

    /*1. fw_cmd*/
    /*Clear go bit*/
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, FW_TUNING_CTRL_ADDR, FW_TUNING_CTRL_GO_MASK, 0));

    /*Disables AN/LT lane swapping*/
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x8440, 0x7fff, 0));

    /*set LT ctle init 6*/
    CTC_ERROR_RETURN(sys_tmm_serdes_set_fw_reg(lchip, serdes_id, 2, 0x600, 4));

    if(SYS_TMM_SERDES_PAM4_MODE == serdes_cfg.pam4_or_nrz)
    {
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_config_speed_pam4(lchip, p_data, &serdes_cfg));
    }
    else
    {
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_config_speed_nrz(lchip, p_data, &serdes_cfg));
    }

    /*upated overclock-speed soft tables    move to upper call stack*/

    /*2. fw_reg(2) == 0x0123*/
    /*uml donot care*/
#ifndef EMULATION_ENV
    if(1 != SDK_WORK_PLATFORM)
    {
        while(times--)
        {
            CTC_ERROR_RETURN(sys_tmm_serdes_get_fw_reg(lchip, serdes_id, 2, 0, &response));
            response = (response > 0x8000) ? (response - 0xffff) : response;
            SYS_CONDITION_BREAK(0x0123 == response);
            sal_task_sleep(1);
        }
        SYS_CONDITION_RETURN(SYS_TMM_USELESS_ID8 == times, CTC_E_HW_NOT_LOCK);
    }
#endif

    /*3. do logic reset*/
    CTC_ERROR_RETURN(sys_tmm_serdes_set_domain_reset(lchip, serdes_id, SYS_TMM_SERDES_LOGIC_RESET));

    /*4. Set go bit*/
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, FW_TUNING_CTRL_ADDR, FW_TUNING_CTRL_GO_MASK, 1));

    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_fw_deconfig(uint8 lchip, uint16 serdes_id)
{
    uint16 result = 0;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM, "lchip %u, serdes_id %u\n", lchip, serdes_id);

    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, 0x9000, 0, 9, &result));

    sal_task_sleep(5);

    return CTC_E_NONE;
}


int16
sys_tmm_serdes_set_serdes_auto_neg_ability(uint8 lchip, uint16 serdes_id, sys_datapath_an_ability_t* p_ability)
{
    uint8 logical_serdes_id = 0;
    sys_datapath_serdes_info_t* p_serdes = NULL;
    sys_datapath_lport_attr_t* port_attr = NULL;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, base page[0]:0x%x, [1]:0x%x, next page1[0]:0x%x, [1]:0x%x\n",
        serdes_id, p_ability->base_ability0, p_ability->base_ability1, p_ability->np1_ability0, p_ability->np1_ability1);

    CTC_ERROR_RETURN(_sys_usw_datapath_get_logical_serdes_id_by_physical(lchip, serdes_id, &logical_serdes_id));
    SYS_CONDITION_RETURN((SYS_TMM_USELESS_ID8 == logical_serdes_id), CTC_E_INVALID_PARAM);
    CTC_ERROR_RETURN(sys_usw_datapath_get_serdes_info(lchip, logical_serdes_id, &p_serdes));
    port_attr = sys_usw_datapath_get_port_capability(lchip, p_serdes->lport);
    if (port_attr == NULL)
    {
        return CTC_E_NOT_INIT;
    }

    port_attr->an_ability = *(p_ability);

    return CTC_E_NONE;
}

int16
_sys_tmm_serdes_set_serdes_auto_neg_ability(uint8 lchip, uint16 serdes_id)
{
    uint8 logical_serdes_id = 0;
    uint16  value_arr[3] = {0};
    sys_datapath_serdes_info_t* p_serdes = NULL;
    sys_datapath_lport_attr_t* port_attr = NULL;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d\n", serdes_id);
    
    CTC_ERROR_RETURN(_sys_usw_datapath_get_logical_serdes_id_by_physical(lchip, serdes_id, &logical_serdes_id));
    SYS_CONDITION_RETURN((SYS_TMM_USELESS_ID8 == logical_serdes_id), CTC_E_INVALID_PARAM);
    CTC_ERROR_RETURN(sys_usw_datapath_get_serdes_info(lchip, logical_serdes_id, &p_serdes));
    port_attr = sys_usw_datapath_get_port_capability(lchip, p_serdes->lport);
    if (port_attr == NULL)
    {
        return CTC_E_NOT_INIT;
    }

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, base page[0]:0x%x, [1]:0x%x, next page1[0]:0x%x, [1]:0x%x\n",
        serdes_id, port_attr->an_ability.base_ability0, port_attr->an_ability.base_ability1,
        port_attr->an_ability.np1_ability0, port_attr->an_ability.np1_ability1);

    /* base page cfg */
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_BASE_PAGE_15_0_ADDR, 0, &value_arr[0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_BASE_PAGE_31_16_ADDR, 0, &value_arr[1]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_BASE_PAGE_47_32_ADDR, 0, &value_arr[2]));

    value_arr[0] &= 0x7FFF;
    value_arr[0] |= (uint16) (port_attr->an_ability.base_ability0 & 0x0000FFFF);
    value_arr[1] &= 0x001F;
    value_arr[1] |= (uint16) (port_attr->an_ability.base_ability0 >> 16);
    value_arr[2] &= 0x0FE0;
    value_arr[2] |= (uint16) (port_attr->an_ability.base_ability1 & 0x0000FFFF);

    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_BASE_PAGE_15_0_ADDR, 0, value_arr[0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_BASE_PAGE_31_16_ADDR, 0, value_arr[1]));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_BASE_PAGE_47_32_ADDR, 0, value_arr[2]));

    if(port_attr->an_ability.np1_ability0 || port_attr->an_ability.np1_ability1)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ARG_ADDR, 0xEFFF, 0));    // Consortium mode

        /* message page cfg */
        value_arr[0] = 0xA005;
        value_arr[1] = 0x0353;
        value_arr[2] = 0x04DF;

        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_MSG_PAGE_15_0_CONSORT_ADDR, 0, value_arr[0]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_MSG_PAGE_31_16_CONSORT_ADDR, 0, value_arr[1]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_MSG_PAGE_47_32_CONSORT_ADDR, 0, value_arr[2]));

        /* unformatted page cfg */
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_UF_PAGE_15_0_CONSORT_ADDR, 0, &value_arr[0]));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_UF_PAGE_31_16_CONSORT_ADDR, 0, &value_arr[1]));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_UF_PAGE_47_32_CONSORT_ADDR, 0, &value_arr[2]));

        value_arr[0] = 0x0203;
        value_arr[1] &= 0xFCCF;
        value_arr[1] |= (uint16) (port_attr->an_ability.np1_ability0 >> 16);
        value_arr[2] &= 0xE01B;
        value_arr[2] |= (uint16) (port_attr->an_ability.np1_ability1 & 0x0000FFFF);

        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_UF_PAGE_15_0_CONSORT_ADDR, 0, value_arr[0]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_UF_PAGE_31_16_CONSORT_ADDR, 0, value_arr[1]));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, AUTO_NEG_UF_PAGE_47_32_CONSORT_ADDR, 0, value_arr[2]));
    }
    else
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, ARG_ADDR, 0xEFFF, 1));    // IEEE mode
    }

    return CTC_E_NONE;
}

int16
sys_tmm_serdes_set_serdes_auto_neg_en(uint8 lchip, uint16 serdes_id, uint32 enable)
{
    uint8 logical_serdes_id = 0;
    uint16 detail = 0;
    uint16 result = 0;
    sys_datapath_serdes_info_t* p_serdes = NULL;
    sys_datapath_lport_attr_t* port_attr = NULL;
    sys_tmm_serdes_serdes_config_param_t p_data;
    sys_tmm_serdes_fw_config_param_t fw_data;

    sal_memset(&p_data, 0, sizeof(sys_tmm_serdes_serdes_config_param_t));
    sal_memset(&fw_data, 0, sizeof(sys_tmm_serdes_fw_config_param_t));

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, enable:0x%X\n", serdes_id, enable);

    CTC_ERROR_RETURN(_sys_usw_datapath_get_logical_serdes_id_by_physical(lchip, serdes_id, &logical_serdes_id));
    SYS_CONDITION_RETURN((SYS_TMM_USELESS_ID8 == logical_serdes_id), CTC_E_INVALID_PARAM);
    CTC_ERROR_RETURN(sys_usw_datapath_get_serdes_info(lchip, logical_serdes_id, &p_serdes));
    port_attr = sys_usw_datapath_get_port_capability(lchip, p_serdes->lport);
    if (port_attr == NULL)
    {
        return CTC_E_NOT_INIT;
    }

    p_data.serdes_id = serdes_id;
    CTC_ERROR_RETURN(sys_tmm_serdes_set_tx_en(lchip, serdes_id, FALSE));
    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_deconfig(lchip, serdes_id));

    /* enable cl73 AN */
    if(enable)
    {
        /* set serdes global with datarate being 25.78125G */
        CTC_ERROR_RETURN(sys_tmm_serdes_set_serdes_globals(lchip, CTC_CHIP_SERDES_XXVG_MODE, CTC_CHIP_SERDES_OCS_MODE_NONE, &p_data));

        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id,
            FW_TUNING_CTRL_ADDR, FW_TUNING_CTRL_GO_MASK, 0));    // clear go bit
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id,
            ANLT_LANE_SWAPPING_REFCLK, 0x7FFF, 0));    // Disables AN/LT lane swapping
        CTC_ERROR_RETURN(_sys_tmm_serdes_config_nrz_fw_tx(lchip, serdes_id, 0, 0));
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, 0x980F, 0xFF7F, 0));    // Confirm that Set Data Rate By Firmware
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_cmd(lchip, serdes_id, 0x8000, detail, 8, &result));
        CTC_ERROR_RETURN(_sys_tmm_serdes_config_nrz_fw_rx(lchip, serdes_id, 0));
        CTC_ERROR_RETURN(_sys_tmm_serdes_config_connection(lchip, &p_data));

        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id,
            ARG_ADDR, 0, 0xE000));    // do a AN reset

        /* AN mode config */
        CTC_ERROR_RETURN(_sys_tmm_serdes_set_serdes_auto_neg_ability(lchip, serdes_id));

        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id,
            FW_TUNING_CTRL_ADDR, FW_TUNING_CTRL_GO_MASK, 1));    // set go bit
    }
    /* disable cl73 AN */
    else
    {
        /* disable AN */
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id,
            AUTO_NEG_CONTROL_ADDR, 0xEFFF, 0));

        /* link without AN */
        fw_data.serdes_id = serdes_id;
        fw_data.data_rate = port_attr->pcs_mode;
        fw_data.ovclk_speed = (uint8)p_serdes->overclocking_speed;
        fw_data.link_train = SYS_TMM_SERDES_LINK_TRAIN_TYPE_DISABLE;
        fw_data.tx_idx = 4;
        CTC_ERROR_RETURN(_sys_tmm_serdes_fw_config(lchip, &fw_data));
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_link_training_en(uint8 lchip, uint16 serdes_id, uint8 enable)
{
    uint8 logical_serdes_id = 0;
    sys_datapath_serdes_info_t* p_serdes = NULL;
    sys_datapath_lport_attr_t* port_attr = NULL;
    sys_tmm_serdes_fw_config_param_t p_data;

    sal_memset(&p_data, 0, sizeof(sys_tmm_serdes_fw_config_param_t));

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "Enter %s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%u, enable:%u\n", serdes_id, enable);

    CTC_ERROR_RETURN(_sys_usw_datapath_get_logical_serdes_id_by_physical(lchip, serdes_id, &logical_serdes_id));
    SYS_CONDITION_RETURN((SYS_TMM_USELESS_ID8 == logical_serdes_id), CTC_E_INVALID_PARAM);
    CTC_ERROR_RETURN(sys_usw_datapath_get_serdes_info(lchip, logical_serdes_id, &p_serdes));
    SYS_CONDITION_RETURN(SYS_USW_MAX_PORT_NUM_PER_CHIP <= p_serdes->lport, CTC_E_INVALID_PARAM);
    port_attr = sys_usw_datapath_get_port_capability(lchip, p_serdes->lport);
    if (port_attr == NULL)
    {
        return CTC_E_NOT_INIT;
    }

    p_data.serdes_id   = serdes_id;
    p_data.data_rate   = port_attr->pcs_mode;
    p_data.ovclk_speed = (uint8)p_serdes->overclocking_speed;
    p_data.link_train  = enable ? SYS_TMM_SERDES_LINK_TRAIN_TYPE_ENABLE : SYS_TMM_SERDES_LINK_TRAIN_TYPE_DISABLE;
    p_data.tx_idx      = 4; /*set ffe to default idx4 when disable LT*/
    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_deconfig(lchip, serdes_id));

    if ((0 == SDK_WORK_PLATFORM) && (0 == SDK_WORK_ENV))
    {
#ifndef EMULATION_ENV
        if(enable) /* enable LT */
        {
            CTC_ERROR_RETURN(_sys_tmm_serdes_fw_config(lchip, &p_data));
        }
        else /* disable LT */
        {
            /*AN disable->lt disable do LINK_TRAINING_CONTROL_ADDR, and other steps will be done in auto_neg disable*/
            /*LT-only flow need LINK_TRAINING_CONTROL_ADDR and FW config*/
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id,
                LINK_TRAINING_CONTROL_ADDR, 0xFFFD, 0));
            if(!p_usw_mac_master[lchip]->mac_prop[p_serdes->lport].cl73_enable)
            {
                CTC_ERROR_RETURN(_sys_tmm_serdes_fw_config(lchip, &p_data));
            }
        }
#endif
    }

    return CTC_E_NONE;
}

int16
sys_tmm_serdes_get_serdes_auto_neg_local_ability(uint8 lchip, uint16 serdes_id, sys_datapath_an_ability_t* p_ability)
{
    uint8 logical_serdes_id = 0;
    sys_datapath_serdes_info_t* p_serdes = NULL;
    sys_datapath_lport_attr_t* port_attr = NULL;

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d, base page[0]:0x%x, [1]:0x%x, next page1[0]:0x%x, [1]:0x%x\n",
        serdes_id, p_ability->base_ability0, p_ability->base_ability1, p_ability->np1_ability0, p_ability->np1_ability1);

    CTC_ERROR_RETURN(_sys_usw_datapath_get_logical_serdes_id_by_physical(lchip, serdes_id, &logical_serdes_id));
    SYS_CONDITION_RETURN((SYS_TMM_USELESS_ID8 == logical_serdes_id), CTC_E_INVALID_PARAM);
    CTC_ERROR_RETURN(sys_usw_datapath_get_serdes_info(lchip, logical_serdes_id, &p_serdes));
    port_attr = sys_usw_datapath_get_port_capability(lchip, p_serdes->lport);
    if (port_attr == NULL)
    {
        return CTC_E_NOT_INIT;
    }

    *p_ability = port_attr->an_ability;

    return CTC_E_NONE;
}

int16
sys_tmm_serdes_get_serdes_auto_neg_remote_ability(uint8 lchip, uint16 serdes_id, sys_datapath_an_ability_t* p_ability)
{
    uint16  value_arr[3] = {0};

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d\n", serdes_id);

    sal_memset(p_ability, 0, sizeof(sys_datapath_an_ability_t));

    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_BASE_PAGE_15_0_ADDR, 0, &value_arr[0]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_BASE_PAGE_31_16_ADDR, 0, &value_arr[1]));
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_BASE_PAGE_47_32_ADDR, 0, &value_arr[2]));

    p_ability->base_ability0 = ((uint32) (value_arr[1] << 16)) | ((uint32) value_arr[0]);
    p_ability->base_ability1 = (uint32) value_arr[2];

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "Get remote base page ability, raw format, ability[0] = 0x%x, [1] = 0x%x\n",
        p_ability->base_ability0, p_ability->base_ability1);

    if (value_arr[0] & 0x8000)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_MSG_PAGE_15_0_ADDR, 0, &value_arr[0]));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_MSG_PAGE_31_16_ADDR, 0, &value_arr[1]));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_MSG_PAGE_47_32_ADDR, 0, &value_arr[2]));

        p_ability->np0_ability0 = ((uint32) (value_arr[1] << 16)) | ((uint32) value_arr[0]);
        p_ability->np0_ability1 = (uint32) value_arr[2];

        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "Get remote next page0 ability, raw format, ability[0] = 0x%x, [1] = 0x%x\n",
                p_ability->np0_ability0, p_ability->np0_ability1);

        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_UF_PAGE_15_0_ADDR, 0, &value_arr[0]));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_UF_PAGE_31_16_ADDR, 0, &value_arr[1]));
        CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, AUTO_NEG_RX_UF_PAGE_47_32_ADDR, 0, &value_arr[2]));

        p_ability->np1_ability0 = ((uint32) (value_arr[1] << 16)) | ((uint32) value_arr[0]);
        p_ability->np1_ability1 = (uint32) value_arr[2];

        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "Get remote next page1 ability, raw format, ability[0] = 0x%x, [1] = 0x%x\n",
                p_ability->np1_ability0, p_ability->np1_ability1);
    }
    else
    {
        p_ability->np0_ability0 = 0;
        p_ability->np0_ability1 = 0;
        p_ability->np1_ability0 = 0;
        p_ability->np1_ability1 = 0;
    }

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,
        "serdes %d remote base page ability:0x%x, 0x%x, next page0 ability:0x%x, 0x%x, next page1 ability:0x%x, 0x%x\n",
        serdes_id, p_ability->base_ability0, p_ability->base_ability1,
        p_ability->np0_ability0, p_ability->np0_ability1,
        p_ability->np1_ability0, p_ability->np1_ability1);

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_get_link_training_status(uint8 lchip, uint16 serdes_id, uint16* p_value)
{
    uint16 status_tmp[3]  = {0};
    uint32 status  = 0;
    uint8  lane_id = SYS_TMM_MAP_SERDES_TO_LANE_ID(serdes_id);

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"serdes_id:%d\n", serdes_id);

    CTC_ERROR_RETURN(_sys_tmm_serdes_get_link_training_status(lchip, serdes_id, status_tmp));
    if (0 == (status_tmp[0] & 0x0002))
    {
        status = SYS_PORT_CL72_DISABLE;
    }
    else if (status_tmp[1] & 0x0004)
    {
        status = SYS_PORT_CL72_OK;
    }
    else if (status_tmp[2] & 0x0080)
    {
        status = SYS_PORT_CL72_FAIL;
    }
    else
    {
        status = SYS_PORT_CL72_PROGRESS;
    }

    *p_value = status << (2 * (lane_id % 8));
    SYS_CL73_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"Fixed LT status %u\n", *p_value);

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_set_power_en(uint8 lchip, uint16 serdes_id, uint8 enable)
{
    uint8 index = 0;
    uint16 val = 0;
    uint16 reg_list[6] = {0x00f7, 0x00f7, 0x00ec, 0x00e9, 0x00e3, 0x00ff};
    uint16 mask_list[6] = {0x7fff, 0xbfff, 0xfffd, 0xfffb, 0xdfff, 0xffef};

    /*cpumac keep power up*/
    SYS_CONDITION_RETURN((SYS_TMM_CPUMAC_SERDES_START_ID <= serdes_id) && (!enable), CTC_E_NONE);

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);

    val = enable ? 1 : 0;

    for(index = 0; index < 5; index ++)
    {
        CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, reg_list[index], mask_list[index], val));
    }

    /*when special serdes do power down, don't configure 0xff[4]*/
    switch(serdes_id)
    {
        case 39:  /*MAC2DP0 F7*/
        case 43:  /*MAC3DP0 F3*/
        case 47:  /*MAC3DP0 F7*/
        case 87:  /*MAC2DP1 F7*/
        case 91:  /*MAC3DP1 F3*/
        case 95:  /*MAC3DP1 F7*/
        case 98:  /*CPUMAC  F2*/
            break;
        default:
            CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id, reg_list[5], mask_list[5], val));
            break;
    }

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);
    
    return CTC_E_NONE;
}

uint8 
_sys_tmm_serdes_is_fw_running(uint8 lchip, uint8 serdes_id)
{
    uint8 stat = 0;
    uint16 val_0 = 0;
    uint16 val_1 = 0;
    uint16 val_2 = 0;

    /*check the watchdog counter to see if FW is incrementing it*/
    (void)sys_tmm_serdes_read_reg(lchip, serdes_id, FW_WATCHDOG_TIMER_ADDR, 0x0, &val_0);
    sal_task_sleep(1800); 
    (void)sys_tmm_serdes_read_reg(lchip, serdes_id, FW_WATCHDOG_TIMER_ADDR, 0x0, &val_1);

    /*watchdog counter is moving, then FW is loaded and running*/
    if(val_1 != val_0)  
    {
        /*if FW is loaded but magic word is corrupted, correct it*/
        (void)sys_tmm_serdes_read_reg(lchip, serdes_id, 0xb0, 0xfffd, &val_2);/*0xb0[1]*/
        if(val_2) //NRZ
        {
            (void)sys_tmm_serdes_write_reg(lchip, serdes_id, FW_LOAD_MAGIC_WORD_ADDR, 0x0, FW_LOAD_MAGIC_WORD_NRZ);
        }
        else //PAM4
        {
            (void)sys_tmm_serdes_write_reg(lchip, serdes_id, FW_LOAD_MAGIC_WORD_ADDR, 0x0, FW_LOAD_MAGIC_WORD_PAM4);
        }
        /*watchdog counter is moving, then FW is loaded and running*/
        stat = 1;  
    }
    else
    {
        /*watchdog counter is not moving, then FW is not loaded or halted*/
        stat = 0;  
    }

    return stat;
}

int32
_sys_tmm_serdes_set_sm_reset(uint8 lchip, uint8 serdes_id, uint8 is_pam4)
{
    uint16 addr = 0;
    uint16 mask = 0;
    
    if(is_pam4)
    {
        addr = 0x0000;
        mask = 0x7fff;
    }
    else
    {
        addr = 0x0181;
        mask = 0xf7ff;
    }
    (void)sys_tmm_serdes_write_reg(lchip, serdes_id, addr, mask, 1);
    (void)sys_tmm_serdes_write_reg(lchip, serdes_id, addr, mask, 0);

    return CTC_E_NONE;
}

int32
_sys_tmm_serdes_fw_tuning_recover(uint8 lchip, uint16 lport, uint8 serdes_id, uint8 pcs_mode)
{
    uint8  sigdet    = 0;
    uint8  lpbk_en   = 0;
    uint32 pcs_link  = 0;
    uint16 adp_stat  = 0;
    uint16 ctle_gain = 0;

    /*skip serdes inter loopback || no signal || pcs is up*/
    CTC_ERROR_RETURN(sys_tmm_serdes_get_internal_loopback_pcs(lchip, serdes_id, &lpbk_en));
    SYS_CONDITION_RETURN(lpbk_en, CTC_E_NONE);

    CTC_ERROR_RETURN(sys_tmm_serdes_get_signal_detect(lchip, serdes_id, NULL, &sigdet));
    SYS_CONDITION_RETURN(FALSE == sigdet, CTC_E_NONE);

    CTC_ERROR_RETURN(_sys_tmm_mac_get_pcs_link_status(lchip, lport, &pcs_link));
    SYS_CONDITION_RETURN(1 == pcs_link, CTC_E_NONE);

    /*gain1 = 0x00f5[14:8], gain2 = 0x00f5[6:0]*/
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, CTLE_GAIN1_ADDR, 0, &ctle_gain));

    /*Reg 0x9811:
    Bit 0:   RX adaptation is running
    Bit 1:   RX adaptation is running and in fine stage
    Bit 2:   RX adaptation is OK
    Bit 3:   RX adaptation has error
    Bit 4:   Background tuning and data collection is on hold
    Bit 5:   Link quick recover is armed and ready
    Bit 6~8: Background tuning error
    */
    CTC_ERROR_RETURN(sys_tmm_serdes_read_reg(lchip, serdes_id, FW_TUNING_STATUS_ADDR, 0x0, &adp_stat));

    /*do tuning recover*/
    /*(1 == gain1) && (1 == gain2) && (RX adaption is done)*/
    if(SYS_BMP_IS_SET(0x0101, (ctle_gain & 0x7f7f)) && (0 != (adp_stat & 0x000c)))
    {
        SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_INFO, "[FW tuning recover] serdes %u, 0x00f5 value 0x%04x, 0x9811 value 0x%04x\n", 
            serdes_id, ctle_gain, adp_stat);
        CTC_ERROR_RETURN(_sys_tmm_serdes_set_sm_reset(lchip, serdes_id, SYS_TMM_MODE_IS_PAM4(pcs_mode)));
    }

    return CTC_E_NONE;
}

int32
sys_tmm_serdes_init(uint8 lchip, uint8 serdes_id, uint8 serdes_mode)
{
    sys_tmm_serdes_fw_config_param_t fw_param;

    sal_memset(&fw_param, 0, sizeof(sys_tmm_serdes_fw_config_param_t));

    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_FUNC, "%s\n", __FUNCTION__);
    SYS_DATAPATH_DBG_OUT(CTC_DEBUG_LEVEL_PARAM,"lchip %u, serdes_id %u, serdes_mode %u\n", lchip, serdes_id, serdes_mode);

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s Start \n\n", __FUNCTION__);
    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "lchip %u, serdes_id %u, serdes_mode %u\n", lchip, serdes_id, serdes_mode);
    
    //step1. configure serdes
    fw_param.serdes_id = serdes_id;
    fw_param.data_rate = serdes_mode;
    fw_param.tx_idx = 4; /*4: 20dB ~ 25dB*/
    if ((CTC_CHIP_SERDES_LG_R1_MODE == serdes_mode) || (CTC_CHIP_SERDES_CG_R2_MODE == serdes_mode) ||
        (CTC_CHIP_SERDES_CCG_R4_MODE == serdes_mode) || (CTC_CHIP_SERDES_CDG_R8_MODE == serdes_mode))
    {
         fw_param.ovclk_speed = CTC_CHIP_SERDES_OCS_MODE_51_56G;
    }
    else if(CTC_CHIP_SERDES_XLG_R2_MODE == serdes_mode)
    {
        fw_param.data_rate = CTC_CHIP_SERDES_LG_MODE;
        fw_param.ovclk_speed = CTC_CHIP_SERDES_OCS_MODE_20_625G;
    }

    CTC_ERROR_RETURN(_sys_tmm_serdes_fw_config(lchip, &fw_param));
    CTC_ERROR_RETURN(sys_tmm_serdes_write_reg(lchip, serdes_id,
                LINK_TRAINING_CONTROL_ADDR, 0xfffd, 0));

    TSINGMA_DUMP_PRINT(g_tmm_serdes_init_dump_fp, "\n\n %s End \n\n", __FUNCTION__);

    return CTC_E_NONE;
}

